"""签到 CRUD:状态查询 + 执行签到。

7 天循环规则(2026-06 由 14 天改 7 天一轮,周期长度统一取 SIGNIN_CYCLE_LEN):
  - 昨天签过 → 今天 cycle_day = 昨天 % SIGNIN_CYCLE_LEN + 1,streak += 1(连续)
  - 否则(首签 / 断签) → cycle_day = 1,streak = 1(重置)
今天的 cycle_day 决定发多少金币(档位来自 rewards.get_signin_rewards,运营后台可改)。
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core import rewards
from app.core.rewards import SIGNIN_CYCLE_LEN, cn_today
from app.models.signin import SigninRecord
from app.repositories import wallet as crud_wallet


class AlreadySignedError(Exception):
    """今天已经签过了。"""


@dataclass
class SigninStep:
    day: int  # 1..14
    coin: int
    status: str  # claimed / today / locked


@dataclass
class SigninStatus:
    today_signed: bool
    consecutive_days: int  # 当前已确认的连续签到天数
    today_cycle_day: int  # 今天落在循环的第几档(1..14)
    today_coin: int  # 今天这一档的金币
    can_claim: bool  # 现在能否签到(= not today_signed)
    steps: list[SigninStep]


def _latest_record(db: Session, user_id: int) -> SigninRecord | None:
    stmt = (
        select(SigninRecord)
        .where(SigninRecord.user_id == user_id)
        .order_by(SigninRecord.signin_date.desc())
        .limit(1)
    )
    return db.execute(stmt).scalar_one_or_none()


def _next_cycle_day(last: SigninRecord | None, today) -> int:
    """若现在签到,今天会落在第几档(1..14)。"""
    if last is not None and last.signin_date == today - timedelta(days=1):
        return last.cycle_day % SIGNIN_CYCLE_LEN + 1
    return 1


def get_status(db: Session, user_id: int) -> SigninStatus:
    today = cn_today()
    rewards_list = rewards.get_signin_rewards(db)  # 配置 or 默认(长度=SIGNIN_CYCLE_LEN)
    last = _latest_record(db, user_id)
    today_signed = last is not None and last.signin_date == today

    if today_signed:
        # 14→7 改制遗留:老记录 cycle_day 可能 >SIGNIN_CYCLE_LEN,归一化到 1..LEN 防 rewards_list 越界
        today_cycle_day = (last.cycle_day - 1) % SIGNIN_CYCLE_LEN + 1
        consecutive_days = last.streak
    else:
        today_cycle_day = _next_cycle_day(last, today)
        # 未签时的"已连续"= 还在连续窗口内的既有 streak,否则 0
        if last is not None and last.signin_date == today - timedelta(days=1):
            consecutive_days = last.streak
        else:
            consecutive_days = 0

    steps: list[SigninStep] = []
    for day in range(1, SIGNIN_CYCLE_LEN + 1):
        if today_signed:
            status = "claimed" if day <= today_cycle_day else "locked"
        else:
            if day < today_cycle_day:
                status = "claimed"
            elif day == today_cycle_day:
                status = "today"
            else:
                status = "locked"
        steps.append(SigninStep(day=day, coin=rewards_list[day - 1], status=status))

    return SigninStatus(
        today_signed=today_signed,
        consecutive_days=consecutive_days,
        today_cycle_day=today_cycle_day,
        today_coin=rewards_list[today_cycle_day - 1],
        can_claim=not today_signed,
        steps=steps,
    )


def do_signin(db: Session, user_id: int) -> tuple[SigninRecord, int]:
    """执行签到。返回 (签到记录, 签到后金币余额)。今天已签则抛 AlreadySignedError。"""
    today = cn_today()
    last = _latest_record(db, user_id)
    if last is not None and last.signin_date == today:
        raise AlreadySignedError

    if last is not None and last.signin_date == today - timedelta(days=1):
        cycle_day = last.cycle_day % SIGNIN_CYCLE_LEN + 1
        streak = last.streak + 1
    else:
        cycle_day = 1
        streak = 1

    coin = rewards.get_signin_rewards(db)[cycle_day - 1]
    record = SigninRecord(
        user_id=user_id,
        signin_date=today,
        cycle_day=cycle_day,
        streak=streak,
        coin_awarded=coin,
    )
    db.add(record)
    acc, _ = crud_wallet.grant_coins(
        db, user_id, coin, biz_type="signin", ref_id=today.isoformat(),
        remark=f"每日签到 第{cycle_day}天",
    )
    db.commit()
    db.refresh(record)
    return record, acc.coin_balance
