import Foundation
import Security

/// Stores OAuth tokens in the macOS Data Protection keychain (stable across Xcode rebuilds).
final class KeychainTokenStore {
    private let service: String
    private let account: String
    private let useDataProtectionKeychain: Bool

    init(
        service: String = Bundle.main.bundleIdentifier ?? "meetings_app",
        account: String = "googleOAuthTokens",
        useDataProtectionKeychain: Bool = true
    ) {
        self.service = service
        self.account = account
        self.useDataProtectionKeychain = useDataProtectionKeychain
    }

    private func makeKeychainError(status: OSStatus, message: String) -> NSError {
        let statusMessage = SecCopyErrorMessageString(status, nil) as String? ?? "OSStatus \(status)"
        return NSError(
            domain: NSOSStatusErrorDomain,
            code: Int(status),
            userInfo: [
                NSLocalizedDescriptionKey: message,
                NSLocalizedFailureReasonErrorKey: statusMessage,
                "keychainStatus": Int(status),
                "keychainStatusMessage": statusMessage,
                "keychainService": service,
                "keychainAccount": account
            ]
        )
    }

    private func baseQuery() -> [String: Any] {
        var query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
        if useDataProtectionKeychain {
            query[kSecUseDataProtectionKeychain as String] = true
        }
        return query
    }

    func readTokens() throws -> GoogleOAuthTokens? {
        var query = baseQuery()
        query[kSecReturnData as String] = true
        query[kSecMatchLimit as String] = kSecMatchLimitOne

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        if status == errSecItemNotFound { return nil }
        guard status == errSecSuccess else {
            throw makeKeychainError(status: status, message: "Failed to read OAuth tokens from Keychain.")
        }
        guard let data = result as? Data else {
            throw NSError(
                domain: NSOSStatusErrorDomain,
                code: Int(errSecDecode),
                userInfo: [NSLocalizedDescriptionKey: "Keychain returned an invalid token payload."]
            )
        }
        return try JSONDecoder().decode(GoogleOAuthTokens.self, from: data)
    }

    /// Reads tokens written by older builds in the legacy file-based login keychain.
    func readLegacyFileBasedTokens() throws -> GoogleOAuthTokens? {
        guard useDataProtectionKeychain else { return nil }
        var query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        if status == errSecItemNotFound { return nil }
        guard status == errSecSuccess else { return nil }
        guard let data = result as? Data else { return nil }
        return try JSONDecoder().decode(GoogleOAuthTokens.self, from: data)
    }

    func writeTokens(_ tokens: GoogleOAuthTokens) throws {
        let data = try JSONEncoder().encode(tokens)
        let baseQuery = baseQuery()

        let attributesToUpdate: [String: Any] = [
            kSecValueData as String: data
        ]

        let updateStatus = SecItemUpdate(baseQuery as CFDictionary, attributesToUpdate as CFDictionary)
        if updateStatus == errSecSuccess { return }
        if updateStatus != errSecItemNotFound {
            throw makeKeychainError(status: updateStatus, message: "Failed to update OAuth tokens in Keychain.")
        }

        var addQuery = baseQuery
        addQuery[kSecValueData as String] = data
        addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
        guard addStatus == errSecSuccess else {
            throw makeKeychainError(status: addStatus, message: "Failed to save OAuth tokens to Keychain.")
        }
    }

    func deleteTokens() throws {
        let status = SecItemDelete(baseQuery() as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw makeKeychainError(status: status, message: "Failed to delete OAuth tokens from Keychain.")
        }
    }

    func deleteLegacyFileBasedTokens() {
        guard useDataProtectionKeychain else { return }
        var query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
        _ = SecItemDelete(query as CFDictionary)
    }
}
