// 主 App 与键盘扩展共享的账户态存储（App Group UserDefaults）。
// 状态推导规则与 server/pkg/protocol/dto.go 注释一致：
//   无 token → guest；余额 > 0 → ok；余额 = 0 且试用未尽 → trial；皆尽 → quota。
// 14B：mock 种子已移除，首启即 guest；微信登录 applyLogin 写 token（键盘扩展立即可见），
//      GET /v1/me 经 applyMe 刷新，退出登录 logout。
// 14D：WS usage 帧到达时经 applyUsage 实时回写 balanceSeconds / trialRemainingSeconds。
import Combine
import Foundation

/// 账户四态。ok/trial/quota 与 dto.go AccountOK/AccountTrial/AccountQuota 字面量对齐，
/// guest 为客户端本地态（无 token，服务端无此值）。
public enum AccountState: String, CaseIterable {
    case ok, trial, guest, quota
}

public final class AccountStore: ObservableObject {
    public static let appGroupID = "group.app.dudu.shared"

    private enum Key {
        static let token = "account.token"                    // 登录 token（AuthTokenResponse.token）
        static let nickname = "account.nickname"              // MeResponse.nickname_masked（服务端已脱敏）
        static let balanceSeconds = "account.balance_seconds" // MeResponse.balance_seconds
        static let trialRemaining = "account.trial_remaining" // trial_daily_limit - trial_used_today
        static let trialDailyLimit = "account.trial_daily_limit"
        static let kbFullAccess = "kb.full_access" // 键盘扩展每次出现时回写 hasFullAccess（引导第 2 步检测）
        static let kbSeen = "kb.seen"              // 键盘扩展至少打开过一次（引导第 4 步"试一试"检测）
    }

    private let defaults: UserDefaults

    @Published public private(set) var token: String?
    @Published public private(set) var nicknameMasked: String?
    @Published public private(set) var balanceSeconds: Int
    @Published public private(set) var trialRemainingSeconds: Int
    @Published public private(set) var trialDailyLimitSeconds: Int
    @Published public private(set) var keyboardFullAccess: Bool
    @Published public private(set) var keyboardSeen: Bool

    public init() {
        // App Group 不可用时（无签名的本地调试场景）退回 standard，保证不崩
        let d = UserDefaults(suiteName: Self.appGroupID) ?? .standard
        defaults = d
        token = d.string(forKey: Key.token)
        nicknameMasked = d.string(forKey: Key.nickname)
        balanceSeconds = d.integer(forKey: Key.balanceSeconds)
        trialRemainingSeconds = d.integer(forKey: Key.trialRemaining)
        trialDailyLimitSeconds = (d.object(forKey: Key.trialDailyLimit) as? Int) ?? DuduProtocol.trialDailySeconds
        keyboardFullAccess = d.bool(forKey: Key.kbFullAccess)
        keyboardSeen = d.bool(forKey: Key.kbSeen)
    }

    public var state: AccountState {
        guard token != nil else { return .guest }
        if balanceSeconds > 0 { return .ok }
        if trialRemainingSeconds > 0 { return .trial }
        return .quota
    }

    /// 今日试用已用（秒），UI 显示 "X / 3 分钟" 用
    public var trialUsedSeconds: Int {
        max(0, trialDailyLimitSeconds - trialRemainingSeconds)
    }

    /// 键盘扩展与主 App 是两个进程，UserDefaults 变更不会自动同步到 @Published；
    /// 键盘在 viewWillAppear、主 App 在前台激活时调用。
    public func reload() {
        token = defaults.string(forKey: Key.token)
        nicknameMasked = defaults.string(forKey: Key.nickname)
        balanceSeconds = defaults.integer(forKey: Key.balanceSeconds)
        trialRemainingSeconds = defaults.integer(forKey: Key.trialRemaining)
        trialDailyLimitSeconds = (defaults.object(forKey: Key.trialDailyLimit) as? Int) ?? DuduProtocol.trialDailySeconds
        keyboardFullAccess = defaults.bool(forKey: Key.kbFullAccess)
        keyboardSeen = defaults.bool(forKey: Key.kbSeen)
    }

    // MARK: - 14B 真实账户写入

    /// 微信登录成功：写入 token（App Group，键盘扩展下次出现即可见）。
    public func applyLogin(token: String, nicknameMasked nickname: String) {
        defaults.set(token, forKey: Key.token)
        defaults.set(nickname, forKey: Key.nickname)
        reload()
    }

    /// GET /v1/me 响应落盘：余额 / 试用 / 昵称。
    public func applyMe(_ me: MeResponse) {
        defaults.set(me.nicknameMasked, forKey: Key.nickname)
        defaults.set(Int(me.balanceSeconds), forKey: Key.balanceSeconds)
        defaults.set(me.trialDailyLimit, forKey: Key.trialDailyLimit)
        defaults.set(max(0, me.trialDailyLimit - me.trialUsedToday), forKey: Key.trialRemaining)
        reload()
    }

    /// 14D：WS usage 帧实时回写余额 / 试用。
    /// 语义为「字段存在即覆盖」（含 0）：ServerMsg 用 decodeIfPresent，
    /// balance_seconds=0 显式下发时解码为 0（非 nil），这里写入 0 让余额归零，
    /// state 推导 balance=0 && trial=0 → quota。后端 #16 去掉 omitempty 后 0 会显式下发；
    /// 即便后端仍 omitempty 吞掉 0（字段缺失 → nil → 不覆盖），也由 error 帧
    /// （QUOTA_EXCEEDED）与下次 GET /v1/me 兜底，不会误把非 0 余额清掉。
    public func applyUsage(balanceSeconds: Int64?, trialRemaining: Int?) {
        if let b = balanceSeconds { defaults.set(Int(b), forKey: Key.balanceSeconds) }
        if let t = trialRemaining { defaults.set(t, forKey: Key.trialRemaining) }
        reload()
    }

    /// 退出登录：清 token 与缓存余额，回到 guest。
    public func logout() {
        defaults.removeObject(forKey: Key.token)
        defaults.removeObject(forKey: Key.nickname)
        defaults.set(0, forKey: Key.balanceSeconds)
        defaults.set(0, forKey: Key.trialRemaining)
        reload()
    }

    /// 键盘扩展每次 viewWillAppear 调用：回写完全访问态与"打开过键盘"标记，
    /// 主 App 引导第 2 步（完全访问）/ 第 4 步（试一试）据此自动判定完成。
    public func recordKeyboardActive(fullAccess: Bool) {
        defaults.set(fullAccess, forKey: Key.kbFullAccess)
        defaults.set(true, forKey: Key.kbSeen)
        reload()
    }

    #if DEBUG
    /// mock 写入：主 App 调试卡一键切四态供键盘扩展演示（仅 DEBUG 构建）。
    public func setMock(state: AccountState) {
        switch state {
        case .ok:
            defaults.set("mock-token", forKey: Key.token)
            defaults.set("wang***", forKey: Key.nickname)
            defaults.set(472 * 60, forKey: Key.balanceSeconds)
            defaults.set(120, forKey: Key.trialRemaining)
        case .trial:
            defaults.set("mock-token", forKey: Key.token)
            defaults.set("wang***", forKey: Key.nickname)
            defaults.set(0, forKey: Key.balanceSeconds)
            defaults.set(120, forKey: Key.trialRemaining)
        case .guest:
            defaults.removeObject(forKey: Key.token)
            defaults.removeObject(forKey: Key.nickname)
            defaults.set(0, forKey: Key.balanceSeconds)
            defaults.set(0, forKey: Key.trialRemaining)
        case .quota:
            defaults.set("mock-token", forKey: Key.token)
            defaults.set("wang***", forKey: Key.nickname)
            defaults.set(0, forKey: Key.balanceSeconds)
            defaults.set(0, forKey: Key.trialRemaining)
        }
        reload()
    }
    #endif
}
