package app.dudu.data

import android.content.Context
import android.content.SharedPreferences
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID

/** 账户状态，对齐 server/pkg/protocol/dto.go 的 account_state + 客户端本地的"未登录"。 */
enum class AccountState(val wire: String) {
    OK("ok"),       // 余额 > 0
    TRIAL("trial"), // 余额 = 0 且当日试用未尽
    GUEST("guest"), // 未登录（仅客户端态，服务端无此值）
    QUOTA("quota"); // 试用 + 余额均尽

    companion object {
        fun fromWire(s: String?): AccountState =
            entries.firstOrNull { it.wire == s } ?: GUEST
    }
}

data class AccountSnapshot(
    val state: AccountState,
    val balanceSeconds: Long,
    val trialRemainingSeconds: Int,
    /** 每日试用上限（秒，/v1/me trial_daily_limit），默认 180 = 3 分钟 */
    val trialDailyLimitSeconds: Int,
    /** 今日已用试用（秒，/v1/me trial_used_today） */
    val trialUsedTodaySeconds: Int,
    val token: String?,          // JWT，未登录为 null
    val nicknameMasked: String?,
    val userId: String?,
)

/**
 * 账户仓库：状态持久化在 SharedPreferences，主 App 与 IME Service 同进程共享。
 *
 * 真实路径（13B 起）：微信登录 [setLogin] 写入 JWT → GET /v1/me 经 [applyMe] 刷新；
 * WS usage 帧经 [applyUsage] 实时刷新余额/试用，与 /v1/me 互为补充。
 * mock 路径（保留做离线联调）：[setMockState] 手动切四态，仅 debug 入口可见。
 *
 * TODO(secure-store): JWT 现存明文 SharedPreferences，上线前迁 EncryptedSharedPreferences / Keystore。
 */
class AccountRepository private constructor(context: Context) {

    private val prefs: SharedPreferences =
        context.applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)

    private val _snapshot = MutableStateFlow(load())
    val snapshot: StateFlow<AccountSnapshot> = _snapshot

    // 监听跨组件（主 App ↔ IME）的写入，保持两边实时一致
    private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
        _snapshot.value = load()
    }

    init {
        prefs.registerOnSharedPreferenceChangeListener(listener)
    }

    /** 设备 ID：首次生成 UUID 并持久化，用于 WS X-Device-ID 头。 */
    val deviceId: String
        get() {
            prefs.getString(KEY_DEVICE_ID, null)?.let { return it }
            val id = "and-" + UUID.randomUUID().toString()
            prefs.edit().putString(KEY_DEVICE_ID, id).apply()
            return id
        }

    /**
     * 只持久化 token/balance/trialUsed/limit 四个事实，state 与 trialRemaining 读取时推导
     * （对齐 iOS AccountStore，与 server dto.go 判定规则一致）：
     *  - 无 token → guest；balance>0 → ok；trialRemaining>0 → trial；否则 quota
     *  - trialRemaining = limit - trialUsed（下限 0）
     */
    private fun load(): AccountSnapshot {
        val token = prefs.getString(KEY_TOKEN, null)
        val balance = prefs.getLong(KEY_BALANCE, 0L)
        val limit = prefs.getInt(KEY_TRIAL_LIMIT, DEFAULT_TRIAL_LIMIT)
        val trialUsed = prefs.getInt(KEY_TRIAL_USED, 0)
        val trialRemaining = (limit - trialUsed).coerceAtLeast(0)
        val state = when {
            token == null -> AccountState.GUEST
            balance > 0 -> AccountState.OK
            trialRemaining > 0 -> AccountState.TRIAL
            else -> AccountState.QUOTA
        }
        return AccountSnapshot(
            state = state,
            balanceSeconds = balance,
            trialRemainingSeconds = trialRemaining,
            trialDailyLimitSeconds = limit,
            trialUsedTodaySeconds = trialUsed,
            token = token,
            nicknameMasked = prefs.getString(KEY_NICKNAME, null),
            userId = prefs.getString(KEY_USER_ID, null),
        )
    }

    // ─── 真实登录路径（13B） ─────────────────────────────────────────────────

    /** 微信登录成功（POST /v1/auth/wechat）：写入 JWT 与用户信息，随后应调 /v1/me 校正余额。 */
    fun setLogin(token: String, userId: String, nicknameMasked: String) {
        // state 由 token/balance/trial 推导，写入 token 即从 guest 转出，applyMe 再校正余额
        prefs.edit()
            .putString(KEY_TOKEN, token)
            .putString(KEY_USER_ID, userId)
            .putString(KEY_NICKNAME, nicknameMasked)
            .apply()
    }

    /**
     * GET /v1/me → 刷新余额/试用（字段对齐 protocol.MeResponse，试用单位为秒）。
     * 只落盘事实字段；state 由 [load] 依 token/balance/trial 推导，与服务端 account_state 判定一致。
     */
    fun applyMe(
        userId: String,
        nicknameMasked: String,
        balanceSeconds: Long,
        trialDailyLimit: Int,
        trialUsedToday: Int,
    ) {
        prefs.edit()
            .putString(KEY_USER_ID, userId)
            .putString(KEY_NICKNAME, nicknameMasked)
            .putLong(KEY_BALANCE, balanceSeconds)
            .putInt(KEY_TRIAL_LIMIT, trialDailyLimit)
            .putInt(KEY_TRIAL_USED, trialUsedToday)
            .apply()
    }

    /** 退出登录：清 JWT 与用户信息，回游客态（清 token 后 [load] 即推导为 guest）。 */
    fun logout() {
        prefs.edit()
            .remove(KEY_TOKEN)
            .remove(KEY_USER_ID)
            .remove(KEY_NICKNAME)
            .putLong(KEY_BALANCE, 0L)
            .apply()
    }

    /**
     * WS usage 帧 → 实时刷新余额与试用。只更新事实字段（balance/trialUsed），
     * state 由 [load] 依 balance/trialRemaining 推导（与 dto.go 判定规则一致）。
     */
    fun applyUsage(balanceSeconds: Long, trialRemaining: Int) {
        val limit = prefs.getInt(KEY_TRIAL_LIMIT, DEFAULT_TRIAL_LIMIT)
        prefs.edit()
            .putLong(KEY_BALANCE, balanceSeconds)
            .putInt(KEY_TRIAL_USED, (limit - trialRemaining).coerceAtLeast(0))
            .apply()
    }

    /** 当前是否处于离线 mock 会话（setMockState 写入的假 token，不可对真实后端发请求）。 */
    val offlineMock: Boolean
        get() = prefs.getString(KEY_TOKEN, null) == MOCK_TOKEN

    /** 服务端回 QUOTA_EXCEEDED → 余额清零 + 试用耗尽，[load] 即推导为 quota 态。 */
    fun markQuotaExceeded() {
        val limit = prefs.getInt(KEY_TRIAL_LIMIT, DEFAULT_TRIAL_LIMIT)
        prefs.edit()
            .putLong(KEY_BALANCE, 0L)
            .putInt(KEY_TRIAL_USED, limit)
            .apply()
    }

    /** 服务端回 UNAUTHORIZED → 清 JWT，[load] 即推导为 guest 态。 */
    fun markUnauthorized() {
        prefs.edit()
            .remove(KEY_TOKEN)
            .apply()
    }

    // ─── 离线 mock 开关：无后端时手动切四态做联调（仅 debug UI 暴露入口） ────
    // 真实登录路径见 setLogin/applyMe；本方法保留用于后端不可用时的键盘联调
    fun setMockState(state: AccountState) {
        // 只写事实字段，目标 state 由 [load] 推导（token/balance/trial → state）。
        val e = prefs.edit().putInt(KEY_TRIAL_LIMIT, DEFAULT_TRIAL_LIMIT)
        when (state) {
            AccountState.OK -> e.putLong(KEY_BALANCE, 472L * 60).putInt(KEY_TRIAL_USED, 60)
                .putString(KEY_TOKEN, MOCK_TOKEN).putString(KEY_NICKNAME, "wang***")
            AccountState.TRIAL -> e.putLong(KEY_BALANCE, 0L).putInt(KEY_TRIAL_USED, 60)
                .putString(KEY_TOKEN, MOCK_TOKEN).putString(KEY_NICKNAME, "wang***")
            AccountState.QUOTA -> e.putLong(KEY_BALANCE, 0L).putInt(KEY_TRIAL_USED, DEFAULT_TRIAL_LIMIT)
                .putString(KEY_TOKEN, MOCK_TOKEN).putString(KEY_NICKNAME, "wang***")
            AccountState.GUEST -> e.putLong(KEY_BALANCE, 0L).putInt(KEY_TRIAL_USED, 0)
                .remove(KEY_TOKEN).remove(KEY_NICKNAME)
        }
        e.apply()
    }

    companion object {
        private const val PREFS = "dudu_account"
        private const val KEY_BALANCE = "balance_seconds"
        private const val KEY_TRIAL_LIMIT = "trial_daily_limit_seconds"
        private const val KEY_TRIAL_USED = "trial_used_today_seconds"
        private const val KEY_TOKEN = "token"
        private const val KEY_NICKNAME = "nickname_masked"
        private const val KEY_USER_ID = "user_id"
        private const val KEY_DEVICE_ID = "device_id"

        /** 每日试用 3 分钟（与 server protocol.TrialDailySeconds 一致，/v1/me 会校正）。 */
        private const val DEFAULT_TRIAL_LIMIT = 180

        // 离线 mock 开关用的假 JWT（dev server 不校验时可直接联调键盘 UI）
        private const val MOCK_TOKEN = "dev-mock-token"

        @Volatile
        private var instance: AccountRepository? = null

        fun get(context: Context): AccountRepository =
            instance ?: synchronized(this) {
                instance ?: AccountRepository(context).also { instance = it }
            }
    }
}
