"""钱包相关表:金币账户 + 金币流水。

设计要点:
  - 余额快照(coin_account)用于读取,流水账本(coin_transaction)用于对账,
    每次余额变动都写一笔流水并记 balance_after,出问题能逐笔回溯。
  - 金额一律存整数:金币是个数,现金存"分"(cash_balance_cents),不用浮点。
  - cash_balance_cents 本步先留列、恒为 0,金币兑现金(第 2 步)再启用。
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, func, text
from sqlalchemy.orm import Mapped, mapped_column

from app.db.base import Base


class CoinAccount(Base):
    __tablename__ = "coin_account"

    # 一个用户一行,user_id 既是主键也是外键
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), primary_key=True
    )
    coin_balance: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    cash_balance_cents: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    # 邀请奖励金余额(分)——与金币兑换来的 cash_balance_cents **物理隔离**(产品红线:
    # 邀请奖励金 ≠ 看广告/金币现金,两本账不可累加)。好友比价发奖入账、提现出账走它。
    invite_cash_balance_cents: Mapped[int] = mapped_column(
        Integer, nullable=False, default=0, server_default="0"
    )
    # 累计赚取的金币(只增不减),用于"历史总收益"类展示
    total_coin_earned: Mapped[int] = mapped_column(Integer, nullable=False, default=0)

    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        onupdate=func.now(),
        nullable=False,
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<CoinAccount user_id={self.user_id} coin={self.coin_balance}>"


class CoinTransaction(Base):
    __tablename__ = "coin_transaction"
    __table_args__ = (
        # 可重复任务(如 task_enable_notification)按 ref_id 序号(task_key:N)去重,挡并发/连点
        # 重复发放——读-算-写无锁,两个并发 claim 会都算出同一序号双倍发钱(见 task.claim_task)。
        # 仅 task_* 且 ref_id 非空生效;一次性任务 ref_id=task_key 本就每人一条,纳入无害;
        # 不影响 signin / exchange / withdraw 等其它 biz_type。
        Index(
            "ux_coin_transaction_task_ref",
            "user_id",
            "biz_type",
            "ref_id",
            unique=True,
            sqlite_where=text("biz_type LIKE 'task%' AND ref_id IS NOT NULL"),
            postgresql_where=text("biz_type LIKE 'task%' AND ref_id IS NOT NULL"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), index=True, nullable=False
    )
    # 正数=入账(赚),负数=出账(花/兑换)
    amount: Mapped[int] = mapped_column(Integer, nullable=False)
    # 本笔变动后的金币余额,对账用
    balance_after: Mapped[int] = mapped_column(Integer, nullable=False)
    # 业务类型:signin / task_<key> / exchange_out / ...
    biz_type: Mapped[str] = mapped_column(String(32), nullable=False)
    # 关联业务 id(签到日期、任务 key 等),可空
    ref_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    remark: Mapped[str | None] = mapped_column(String(128), nullable=True)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<CoinTransaction id={self.id} user_id={self.user_id} amount={self.amount}>"


class WithdrawOrder(Base):
    """提现单(现金 → 微信零钱)。状态机:
        reviewing →(管理员通过)→ pending → success / failed
        reviewing →(管理员拒绝)→ rejected(已退款)

    扣现金 + 写 cash_transaction(withdraw) 与建本单在同一事务,初始 reviewing(待人工审核,
    **不打款**);管理员审核通过才调微信转账(execute_withdraw_transfer)→ pending,拒绝则退回
    现金 + 置 rejected。微信侧失败/取消时退回现金并写 cash_transaction(withdraw_refund) → failed。
    wechat_state 存微信侧原始状态(WAIT_USER_CONFIRM / SUCCESS / FAIL / CANCELLED ...)。
    """

    __tablename__ = "withdraw_order"
    __table_args__ = (
        Index(
            "ux_withdraw_order_user_active",
            "user_id",
            unique=True,
            sqlite_where=text("status IN ('reviewing', 'pending')"),
            postgresql_where=text("status IN ('reviewing', 'pending')"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), index=True, nullable=False
    )
    # 商户单号(我们生成,微信查单的 out_bill_no),唯一
    out_bill_no: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
    amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
    # 这笔提现扣的是哪个账户:coin_cash(金币兑换的现金) / invite_cash(邀请奖励金)。
    # 退款时据此退回**对应**账户,两本账不串。旧单默认 coin_cash。
    source: Mapped[str] = mapped_column(
        String(16), nullable=False, default="coin_cash", server_default="coin_cash"
    )
    # 提现实名(微信达额转账要求):审核后异步打款时要用,发起提现时存下,可空
    user_name: Mapped[str | None] = mapped_column(String(64), nullable=True)
    # 归一化状态:reviewing(待审核) / pending(打款在途) / success / failed(打款失败已退) / rejected(审核拒绝已退)
    status: Mapped[str] = mapped_column(String(16), nullable=False, default="reviewing")
    # 微信侧原始状态
    wechat_state: Mapped[str | None] = mapped_column(String(32), nullable=True)
    # 微信转账单号
    transfer_bill_no: Mapped[str | None] = mapped_column(String(64), nullable=True)
    # 待用户确认时返回给 App 拉起确认页的 package_info
    package_info: Mapped[str | None] = mapped_column(String(512), nullable=True)
    fail_reason: Mapped[str | None] = mapped_column(String(256), nullable=True)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<WithdrawOrder id={self.id} user_id={self.user_id} cents={self.amount_cents} {self.status}>"


class WechatTransferAuthorization(Base):
    """微信商家转账「免确认收款授权」(用户授权免确认模式)。一个用户一条(user_id 主键)。

    状态机:
        (无) →(申请 / 首单转账顺带申请)→ pending(微信 WAIT_USER_CONFIRM,待用户在微信确认授权)
        pending →(用户确认授权)→ active(微信 TAKING_EFFECT,此后转账免用户逐笔确认)
        pending / active →(用户在微信关 / 商户解除 / 风控)→ closed(终态,需重新开启)
    out_authorization_no 我方生成(查授权 / 发起授权用,一个用户一条稳定值,重开时换新);
    authorization_id 微信在 active 后返回,免确认转账(transfer_with_authorization)时必传。
    """

    __tablename__ = "wechat_transfer_authorization"

    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), primary_key=True
    )
    openid: Mapped[str] = mapped_column(String(64), nullable=False)
    # 商户侧授权单号(我方生成),唯一
    out_authorization_no: Mapped[str] = mapped_column(
        String(64), unique=True, index=True, nullable=False
    )
    # 微信侧授权单号(active 后返回,免确认转账要用)
    authorization_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    # pending(待用户确认) / active(已生效可免确认) / closed(已关闭需重开)
    state: Mapped[str] = mapped_column(String(16), nullable=False, default="pending")

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<WechatTransferAuthorization user_id={self.user_id} state={self.state}>"


class CashTransaction(Base):
    """现金流水(单位:分)。金币兑现金、提现都记在这里。"""

    __tablename__ = "cash_transaction"
    __table_args__ = (
        Index(
            "ux_cash_transaction_withdraw_refund_ref",
            "ref_id",
            unique=True,
            sqlite_where=text("biz_type = 'withdraw_refund' AND ref_id IS NOT NULL"),
            postgresql_where=text("biz_type = 'withdraw_refund' AND ref_id IS NOT NULL"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), index=True, nullable=False
    )
    # 正数=入账(兑入),负数=出账(提现)
    amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
    balance_after_cents: Mapped[int] = mapped_column(Integer, nullable=False)
    # 业务类型:exchange_in / withdraw
    biz_type: Mapped[str] = mapped_column(String(32), nullable=False)
    ref_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    remark: Mapped[str | None] = mapped_column(String(128), nullable=True)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<CashTransaction id={self.id} user_id={self.user_id} cents={self.amount_cents}>"


class InviteCashTransaction(Base):
    """邀请奖励金流水(单位:分)。与 cash_transaction(金币兑换现金)**物理隔离**——
    产品红线:邀请奖励金 ≠ 看广告/金币现金,两本账不可累加、各自提现。
    入账=好友比价发奖(invite_reward),出账=提现(invite_withdraw)/退款(invite_withdraw_refund)。
    结构与 cash_transaction 同构,balance_after_cents 记的是 coin_account.invite_cash_balance_cents。"""

    __tablename__ = "invite_cash_transaction"
    __table_args__ = (
        # 提现退款幂等:一个提现单只退一次(同 cash_transaction 的 withdraw_refund 去重)
        Index(
            "ux_invite_cash_txn_refund_ref",
            "ref_id",
            unique=True,
            sqlite_where=text("biz_type = 'invite_withdraw_refund' AND ref_id IS NOT NULL"),
            postgresql_where=text("biz_type = 'invite_withdraw_refund' AND ref_id IS NOT NULL"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), index=True, nullable=False
    )
    # 正数=入账(发奖),负数=出账(提现)
    amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
    balance_after_cents: Mapped[int] = mapped_column(Integer, nullable=False)
    # 业务类型:invite_reward / invite_withdraw / invite_withdraw_refund
    biz_type: Mapped[str] = mapped_column(String(32), nullable=False)
    ref_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    remark: Mapped[str | None] = mapped_column(String(128), nullable=True)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return f"<InviteCashTransaction id={self.id} user_id={self.user_id} cents={self.amount_cents}>"
