// Sources/Core/Auth/Keychain.swift
// T-2.2: Generic Keychain wrapper (kSecClassGenericPassword)

import Foundation
import Security

// MARK: - Errors

enum KeychainError: Error, Sendable {
    case notFound
    case encodingFailed
    case saveFailed(OSStatus)
}

// MARK: - Keychain

/// Thread-safe Keychain wrapper for `Codable` values stored as JSON data.
/// All items use `kSecClassGenericPassword` with a caller-supplied service key.
final class Keychain: Sendable {

    static let shared = Keychain()

    /// Canonical key used to store the active `SidecarCredential`.
    static let credentialKey = "piremote.credential"

    private init() {}

    // MARK: Save

    /// Encodes `value` as JSON and upserts it into the Keychain under `key`.
    func save<T: Encodable>(_ value: T, key: String) throws {
        let data: Data
        do {
            data = try JSONEncoder().encode(value)
        } catch {
            throw KeychainError.encodingFailed
        }

        // Attempt update first; if item doesn't exist, add it.
        let query = baseQuery(for: key)
        let updateAttributes: [CFString: Any] = [kSecValueData: data]

        let updateStatus = SecItemUpdate(query as CFDictionary, updateAttributes as CFDictionary)
        if updateStatus == errSecItemNotFound {
            var addQuery = query
            addQuery[kSecValueData] = data
            let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
            guard addStatus == errSecSuccess else {
                throw KeychainError.saveFailed(addStatus)
            }
        } else if updateStatus != errSecSuccess {
            throw KeychainError.saveFailed(updateStatus)
        }
    }

    // MARK: Load

    /// Loads and JSON-decodes a value of type `T` stored under `key`.
    func load<T: Decodable>(key: String) throws -> T {
        var query = baseQuery(for: key)
        query[kSecReturnData] = true
        query[kSecMatchLimit] = kSecMatchLimitOne

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        guard status == errSecSuccess else {
            throw KeychainError.notFound
        }
        guard let data = result as? Data else {
            throw KeychainError.notFound
        }
        do {
            return try JSONDecoder().decode(T.self, from: data)
        } catch {
            throw KeychainError.encodingFailed
        }
    }

    // MARK: Delete

    /// Removes the item stored under `key` (no-op if absent).
    func delete(key: String) {
        SecItemDelete(baseQuery(for: key) as CFDictionary)
    }

    // MARK: - Private helpers

    private func baseQuery(for key: String) -> [CFString: Any] {
        [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: "de.vpsj.pi-remote",
            kSecAttrAccount: key,
            kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
        ]
    }
}
