import Foundation
import OSLog
import SecretKit
import SSHProtocolKit
import Common

/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
public final class PublicKeyFileStoreController: Sendable {

    private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
    private let directory: URL
    private let keyWriter = OpenSSHPublicKeyWriter()

    /// Initializes a PublicKeyFileStoreController.
    public init(directory: URL) {
        self.directory = directory
    }

    /// Writes out the keys specified to disk.
    /// - Parameter secrets: The Secrets to generate keys for.
    /// - Parameter clear: Whether or not any untracked files in the directory should be removed.
    public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
        logger.log("Writing public keys to disk")
        if clear {
            let validPaths = Set(secrets.map { URL.publicKeyPath(for: $0, in: directory) })
                .union(Set(secrets.map { sshCertificatePath(for: $0) }))
            let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
            let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }

            let untracked = Set(fullPathContents)
                .subtracting(validPaths)
            for path in untracked {
                // string instead of fileURLWithPath since we're already using fileURL format.
                try? FileManager.default.removeItem(at: URL(string: path)!)
            }
        }
        try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
        for secret in secrets {
            let path = URL.publicKeyPath(for: secret, in: directory)
            let data = Data(keyWriter.openSSHString(secret: secret).utf8)
            FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
        }
        logger.log("Finished writing public keys")
    }


    /// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
    public var hasAnyCertificates: Bool {
        do {
            return try FileManager.default
                .contentsOfDirectory(atPath: directory.path())
                .filter { $0.hasSuffix("-cert.pub") }
                .isEmpty == false
        } catch {
            return false
        }
    }

    /// The path for a Secret's SSH Certificate public key.
    /// - Parameter secret: The Secret to return the path for.
    /// - Returns: The path to the SSH Certificate public key.
    /// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be.
    public func sshCertificatePath<SecretType: Secret>(for secret: SecretType) -> String {
        let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
        return directory.appending(component: "\(minimalHex)-cert.pub").path()
    }

}
