//
//  SessionTokenStore.swift
//  suixinkan
//
//  Created by Codex on 2026/6/20.
//

import Foundation
import Observation
import Security

/// 登录 token 存储错误实体，表示 Keychain 读写失败的具体状态。
enum SessionTokenStoreError: LocalizedError {
    case unexpectedStatus(OSStatus)

    var errorDescription: String? {
        switch self {
        case let .unexpectedStatus(status):
            "登录凭证存储失败（\(status)）"
        }
    }
}

@Observable
/// 正式登录 token 存储服务，封装 Keychain 读写并避免业务层接触安全 API。
final class SessionTokenStore {
    @ObservationIgnored private let service: String
    @ObservationIgnored private let account: String

    /// 初始化 token 存储服务，默认按 App Bundle 隔离 Keychain 项。
    init(
        service: String = Bundle.main.bundleIdentifier ?? "com.yuanzhixiang.suixinkan",
        account: String = "session.token"
    ) {
        self.service = service
        self.account = account
    }

    /// 保存正式 token，空 token 会被视为清空凭证。
    func save(_ token: String) throws {
        let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedToken.isEmpty else {
            try clear()
            return
        }

        let data = Data(trimmedToken.utf8)
        let query = baseQuery()
        let updateAttributes: [String: Any] = [kSecValueData as String: data]

        let updateStatus = SecItemUpdate(query as CFDictionary, updateAttributes as CFDictionary)
        if updateStatus == errSecSuccess {
            return
        }
        guard updateStatus == errSecItemNotFound else {
            throw SessionTokenStoreError.unexpectedStatus(updateStatus)
        }

        var addQuery = query
        updateAttributes.forEach { addQuery[$0.key] = $0.value }
        addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
        guard addStatus == errSecSuccess else {
            throw SessionTokenStoreError.unexpectedStatus(addStatus)
        }
    }

    /// 读取本地正式 token，读取失败或无值时返回 nil。
    func load() -> String? {
        var query = baseQuery()
        query[kSecReturnData as String] = true
        query[kSecMatchLimit as String] = kSecMatchLimitOne

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        guard status == errSecSuccess,
              let data = result as? Data,
              let token = String(data: data, encoding: .utf8)?
                .trimmingCharacters(in: .whitespacesAndNewlines),
              !token.isEmpty else {
            return nil
        }
        return token
    }

    /// 清空本地正式 token。
    func clear() throws {
        let status = SecItemDelete(baseQuery() as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw SessionTokenStoreError.unexpectedStatus(status)
        }
    }

    /// 构造当前 App 使用的 Keychain 查询条件。
    private func baseQuery() -> [String: Any] {
        [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account
        ]
    }
}
