import CodexBarCore
import Foundation
import Security

protocol CookieHeaderStoring: Sendable {
    func loadCookieHeader() throws -> String?
    func storeCookieHeader(_ header: String?) throws
}

enum CookieHeaderStoreError: LocalizedError {
    case keychainStatus(OSStatus)
    case invalidData

    var errorDescription: String? {
        switch self {
        case let .keychainStatus(status):
            "Keychain error: \(status)"
        case .invalidData:
            "Keychain returned invalid data."
        }
    }
}

struct KeychainCookieHeaderStore: CookieHeaderStoring {
    private static let log = CodexBarLog.logger(LogCategories.cookieHeaderStore)

    private let service = "com.steipete.CodexBar"
    private let account: String
    private let promptKind: KeychainPromptContext.Kind

    // Cache to reduce keychain access frequency
    private nonisolated(unsafe) static var cache: [String: CachedValue] = [:]
    private static let cacheLock = NSLock()
    private static let cacheTTL: TimeInterval = 1800 // 30 minutes

    private struct CachedValue {
        let value: String?
        let timestamp: Date

        var isExpired: Bool {
            Date().timeIntervalSince(self.timestamp) > KeychainCookieHeaderStore.cacheTTL
        }
    }

    init(account: String, promptKind: KeychainPromptContext.Kind) {
        self.account = account
        self.promptKind = promptKind
    }

    func loadCookieHeader() throws -> String? {
        guard !KeychainAccessGate.isDisabled else {
            Self.log.debug("Keychain access disabled; skipping cookie load")
            return nil
        }
        // Check cache first
        Self.cacheLock.lock()
        if let cached = Self.cache[self.account], !cached.isExpired {
            Self.cacheLock.unlock()
            Self.log.debug("Using cached cookie header for \(self.account)")
            return cached.value
        }
        Self.cacheLock.unlock()
        var result: CFTypeRef?
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: self.service,
            kSecAttrAccount as String: self.account,
            kSecMatchLimit as String: kSecMatchLimitOne,
            kSecReturnData as String: true,
        ]

        if case .interactionRequired = KeychainAccessPreflight
            .checkGenericPassword(service: self.service, account: self.account)
        {
            KeychainPromptHandler.handler?(KeychainPromptContext(
                kind: self.promptKind,
                service: self.service,
                account: self.account))
        }

        let status = KeychainSecurity.copyMatching(query as CFDictionary, &result)
        if status == errSecItemNotFound {
            // Cache the nil result
            Self.cacheLock.lock()
            Self.cache[self.account] = CachedValue(value: nil, timestamp: Date())
            Self.cacheLock.unlock()
            return nil
        }
        guard status == errSecSuccess else {
            Self.log.error("Keychain read failed: \(status)")
            throw CookieHeaderStoreError.keychainStatus(status)
        }

        guard let data = result as? Data else {
            throw CookieHeaderStoreError.invalidData
        }
        let header = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let finalValue = (header?.isEmpty == false) ? header : nil

        // Cache the result
        Self.cacheLock.lock()
        Self.cache[self.account] = CachedValue(value: finalValue, timestamp: Date())
        Self.cacheLock.unlock()

        return finalValue
    }

    func storeCookieHeader(_ header: String?) throws {
        guard !KeychainAccessGate.isDisabled else {
            Self.log.debug("Keychain access disabled; skipping cookie store")
            return
        }
        guard let raw = header?.trimmingCharacters(in: .whitespacesAndNewlines),
              !raw.isEmpty
        else {
            try self.deleteIfPresent()
            // Invalidate cache
            Self.cacheLock.lock()
            Self.cache.removeValue(forKey: self.account)
            Self.cacheLock.unlock()
            return
        }
        guard CookieHeaderNormalizer.normalize(raw) != nil else {
            try self.deleteIfPresent()
            // Invalidate cache
            Self.cacheLock.lock()
            Self.cache.removeValue(forKey: self.account)
            Self.cacheLock.unlock()
            return
        }

        let data = raw.data(using: .utf8)!
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: self.service,
            kSecAttrAccount as String: self.account,
        ]
        let attributes: [String: Any] = [
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
        ]

        let updateStatus = KeychainSecurity.update(query as CFDictionary, attributes as CFDictionary)
        if updateStatus == errSecSuccess {
            // Update cache
            Self.cacheLock.lock()
            Self.cache[self.account] = CachedValue(value: raw, timestamp: Date())
            Self.cacheLock.unlock()
            return
        }
        if updateStatus != errSecItemNotFound {
            Self.log.error("Keychain update failed: \(updateStatus)")
            throw CookieHeaderStoreError.keychainStatus(updateStatus)
        }

        var addQuery = query
        for (key, value) in attributes {
            addQuery[key] = value
        }
        let addStatus = KeychainSecurity.add(addQuery as CFDictionary, nil)
        guard addStatus == errSecSuccess else {
            Self.log.error("Keychain add failed: \(addStatus)")
            throw CookieHeaderStoreError.keychainStatus(addStatus)
        }

        // Update cache
        Self.cacheLock.lock()
        Self.cache[self.account] = CachedValue(value: raw, timestamp: Date())
        Self.cacheLock.unlock()
    }

    private func deleteIfPresent() throws {
        guard !KeychainAccessGate.isDisabled else { return }
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: self.service,
            kSecAttrAccount as String: self.account,
        ]
        let status = KeychainSecurity.delete(query as CFDictionary)
        if status == errSecSuccess || status == errSecItemNotFound {
            // Invalidate cache
            Self.cacheLock.lock()
            Self.cache.removeValue(forKey: self.account)
            Self.cacheLock.unlock()
            return
        }
        Self.log.error("Keychain delete failed: \(status)")
        throw CookieHeaderStoreError.keychainStatus(status)
    }
}
