"""JWT Token 模型 —— jwt_tokens 表。

记录每次签发的 Token 生命周期：签发、刷新、撤销。
"""

from __future__ import annotations

from datetime import datetime

from sqlalchemy import BigInteger, Boolean, DateTime, String, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Mapped, mapped_column

from fastapi_common.fastapi_common_web.models import BaseModel


class JwtToken(BaseModel):
    """JWT Token 记录表。"""

    __tablename__ = "jwt_tokens"

    Id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
    userId: Mapped[int] = mapped_column(BigInteger, comment="用户ID")
    tokenJti: Mapped[str] = mapped_column(String(128), comment="Token JTI")
    tokenHash: Mapped[str] = mapped_column(String(128), comment="Access Token SHA256")
    refreshTokenHash: Mapped[str | None] = mapped_column(String(128), comment="Refresh Token SHA256")
    tokenType: Mapped[str] = mapped_column(String(32), default="ACCESS", comment="ACCESS/REFRESH")
    deviceId: Mapped[str | None] = mapped_column(String(128))
    deviceName: Mapped[str | None] = mapped_column(String(256))
    userAgent: Mapped[str | None] = mapped_column(String(512))
    ipAddress: Mapped[str | None] = mapped_column(String(64))
    issuedAt: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    expiresAt: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    refreshExpiresAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    lastUsedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    isRevoked: Mapped[bool] = mapped_column(Boolean, default=False)
    revokedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    revokeReason: Mapped[str | None] = mapped_column(String(256))

    @classmethod
    async def get_by_jti(cls, session: AsyncSession, jti: str) -> "JwtToken | None":
        """按 JTI 查询 Token 记录。"""
        return await session.scalar(select(cls).where(cls.tokenJti == jti))

    @classmethod
    async def revoke_by_jti(cls, session: AsyncSession, jti: str, reason: str = "") -> None:
        """撤销指定 JTI 的 Token。"""
        await session.execute(
            update(cls)
            .where(cls.tokenJti == jti)
            .values(isRevoked=True, revokedAt=datetime.now(), revokeReason=reason)
        )

    @classmethod
    async def revoke_all_user_tokens(cls, session: AsyncSession, userId: int, reason: str = "") -> list[str]:
        """撤销用户的所有活跃 Token，返回被撤销的 JTI 列表。"""
        result = await session.execute(
            select(cls.tokenJti).where(cls.userId == userId, cls.isRevoked == False)
        )
        jtis = [row[0] for row in result.fetchall()]
        await session.execute(
            update(cls)
            .where(cls.userId == userId, cls.isRevoked == False)
            .values(isRevoked=True, revokedAt=datetime.now(), revokeReason=reason)
        )
        return jtis

    @classmethod
    async def cleanup_expired(cls, session: AsyncSession, before: datetime) -> int:
        """清理过期的 Token 记录，返回删除数。"""
        result = await session.execute(
            select(cls).where(cls.expiresAt < before)
        )
        rows = result.scalars().all()
        for row in rows:
            await session.delete(row)
        return len(rows)
