import Foundation
import Security

/// Access-token persistence. The scaffold/preview/unsigned-build path uses InMemoryTokenStore
/// (Keychain on an unsigned binary can prompt or return errSecMissingEntitlement); the signed app
/// uses KeychainStore. Only the ACCESS token is stored — refresh rides the HttpOnly cookie.
public protocol TokenPersistence: Sendable {
    func read() -> String?
    func save(_ token: String) throws
    func delete() throws
}

public final class InMemoryTokenStore: TokenPersistence, @unchecked Sendable {
    private let lock = NSLock()
    private var token: String?
    public init() {}
    public func read() -> String? { lock.withLock { token } }
    public func save(_ token: String) throws { lock.withLock { self.token = token } }
    public func delete() throws { lock.withLock { token = nil } }
}

public struct KeychainStore: TokenPersistence, Sendable {
    private let service: String
    private let account: String

    public init(service: String = "net.hyungi.ds-app", account: String = "access_token") {
        self.service = service
        self.account = account
    }

    public func read() -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne,
        ]
        var item: CFTypeRef?
        guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess,
              let data = item as? Data,
              let token = String(data: data, encoding: .utf8)
        else { return nil }
        return token
    }

    public func save(_ token: String) throws {
        let base: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
        ]
        SecItemDelete(base as CFDictionary) // upsert
        var add = base
        add[kSecValueData as String] = Data(token.utf8)
        add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
        let status = SecItemAdd(add as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw DSError.transport(underlying: "keychain save failed (\(status))")
        }
    }

    public func delete() throws {
        let base: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
        ]
        SecItemDelete(base as CFDictionary)
    }
}
