from __future__ import annotations

from datetime import UTC, datetime

from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.security import create_access_token
from app.models.user import User
from app.services.captcha_service import CaptchaService
from app.services.sms_code_service import SmsCodeService
from app.services.security_state_service import SecurityStateService


class AuthService:
    CAPTCHA_REQUIRED_SECONDS = 60 * 60

    @classmethod
    def send_sms_code(
        cls,
        db: Session,
        phone: str,
        captcha_id: str | None,
        captcha_code: str | None,
        ip: str,
    ) -> None:
        user = cls._get_existing_user(db, phone)
        cls.ensure_user_can_login(user)
        captcha_key = cls._captcha_required_key(phone)
        has_captcha = bool(captcha_id or captcha_code)
        if has_captcha:
            if not captcha_id or not captcha_code:
                raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请完整填写图形验证码")
            CaptchaService.verify(captcha_id, captcha_code)
            SecurityStateService.clear(captcha_key)
        elif SecurityStateService.has_flag(captcha_key):
            raise HTTPException(status_code=428, detail="需要完成图形验证码后再发送短信")

        SecurityStateService.enforce_limit(
            f"rate:sms:ip:{ip}", limit=20, window_seconds=3600, message="当前网络发送验证码过于频繁，请稍后再试"
        )
        SecurityStateService.enforce_limit(
            f"rate:sms:phone:{phone}", limit=1, window_seconds=60, message="验证码发送过于频繁，请一分钟后再试"
        )
        SmsCodeService.send_code(db, phone)
        SecurityStateService.mark_flag(captcha_key, cls.CAPTCHA_REQUIRED_SECONDS)

    @classmethod
    def login_with_sms(cls, db: Session, phone: str, code: str) -> dict:
        failure_key = f"auth:sms:fail:{phone}"
        SecurityStateService.ensure_not_locked(
            failure_key, limit=5, message="验证码连续错误次数过多，请十五分钟后重新获取"
        )
        try:
            SmsCodeService.verify_code(db, phone, code)
        except HTTPException:
            SecurityStateService.mark_flag(cls._captcha_required_key(phone), cls.CAPTCHA_REQUIRED_SECONDS)
            SecurityStateService.record_failure(
                failure_key,
                limit=5,
                lock_seconds=900,
                message="验证码连续错误次数过多，请十五分钟后重新获取",
            )
            raise
        SecurityStateService.clear(failure_key)
        user = cls._get_existing_user(db, phone)
        cls.ensure_user_can_login(user)

        now = datetime.now(UTC).replace(tzinfo=None)
        user.last_login_at = now
        db.add(user)
        db.commit()
        db.refresh(user)

        token, expired_at = create_access_token(
            str(user.id),
            "user",
            extra_claims={"auth_source": "direct"},
        )
        return {"token": token, "expiredAt": expired_at, "user": user}

    @classmethod
    def logout(cls, token_payload: dict) -> None:
        jti = token_payload.get("jti")
        if jti:
            SecurityStateService.revoke_token(str(jti), token_payload.get("exp"))

    @classmethod
    def is_token_revoked(cls, token_payload: dict) -> bool:
        jti = token_payload.get("jti")
        return bool(jti and SecurityStateService.is_token_revoked(str(jti)))

    @classmethod
    def _get_existing_user(cls, db: Session, phone: str) -> User:
        user = db.scalar(select(User).where(User.phone == phone, User.is_deleted == 0))
        if user is not None:
            return user

        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="手机号不在学员名单中，请联系管理员")

    @staticmethod
    def ensure_user_can_login(user: User) -> None:
        now = datetime.now(UTC).replace(tzinfo=None)
        if user.status != 1:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号已禁用")
        if user.effective_at and user.effective_at > now:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号尚未生效")
        if user.expired_at and user.expired_at < now:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号已过期")

    @staticmethod
    def _captcha_required_key(phone: str) -> str:
        return f"auth:sms:captcha_required:{phone}"
