import Foundation
import Security

/// Stores the optional OpenSky Network username + password in the Keychain.
///
/// Anonymous OpenSky access is capped at ~100 requests per 24h per IP.
/// A free OpenSky account bumps the cap to 4000/day, which is the difference
/// between "the tab works for casual viewing" and "the tab keeps refreshing
/// without complaint all day". The Settings screen lets the user paste their
/// OpenSky credentials; we stash them in the Keychain and `OpenSkyClient`
/// reads them on each request.
///
/// Posts `Notification.Name.openSkyCredentialsChanged` after every write so
/// the client can refresh its in-memory copy.
final class OpenSkyCredentials: @unchecked Sendable {
    static let shared = OpenSkyCredentials()

    private let service = "com.flights.app.opensky"
    private let account = "credentials"

    struct Credentials: Sendable, Equatable {
        let username: String
        let password: String
    }

    /// Read the stored credentials, or nil if none have been saved.
    func load() -> Credentials? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnAttributes as String: true,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var item: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess,
              let dict = item as? [String: Any],
              let data = dict[kSecValueData as String] as? Data,
              let password = String(data: data, encoding: .utf8),
              let username = dict[kSecAttrGeneric as String] as? Data,
              let usernameStr = String(data: username, encoding: .utf8)
        else { return nil }
        return Credentials(username: usernameStr, password: password)
    }

    /// Save credentials. Overwrites any existing entry.
    func save(username: String, password: String) {
        let username = username.trimmingCharacters(in: .whitespacesAndNewlines)
        let password = password.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !username.isEmpty, !password.isEmpty else { return }

        let usernameData = username.data(using: .utf8) ?? Data()
        let passwordData = password.data(using: .utf8) ?? Data()

        // Try update first, fall back to add.
        let updateQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
        let updateAttrs: [String: Any] = [
            kSecAttrGeneric as String: usernameData,
            kSecValueData as String: passwordData
        ]
        let status = SecItemUpdate(updateQuery as CFDictionary, updateAttrs as CFDictionary)
        if status == errSecItemNotFound {
            var addQuery = updateQuery
            addQuery[kSecAttrGeneric as String] = usernameData
            addQuery[kSecValueData as String] = passwordData
            SecItemAdd(addQuery as CFDictionary, nil)
        }
        NotificationCenter.default.post(name: .openSkyCredentialsChanged, object: nil)
    }

    /// Remove any stored credentials (back to anonymous).
    func clear() {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
        SecItemDelete(query as CFDictionary)
        NotificationCenter.default.post(name: .openSkyCredentialsChanged, object: nil)
    }
}

extension Notification.Name {
    static let openSkyCredentialsChanged = Notification.Name("openSkyCredentialsChanged")
}
