"""
認證相關 API:註冊 / 登入 / refresh token。

設計重點:
  1. email 一律存小寫,避免 "Robert@x.com" 跟 "robert@x.com" 被當成兩個帳號
  2. 第一個註冊的人自動成為 owner,後續都是 member
     (注意:Docker 啟動時 seed 已建立第一個 owner,實務上這條規則
      只在「跳過 seed、從零註冊」時才會觸發,但保留邏輯讓系統有自癒能力)
  3. 登入失敗的訊息一律「帳號或密碼錯誤」,不告訴攻擊者「這個 email 不存在」
"""

from fastapi import APIRouter, HTTPException, status
from sqlalchemy import func, select

from app.core.deps import DBSession
from app.core.security import (
    TokenError,
    create_access_token,
    create_refresh_token,
    decode_token,
    hash_password,
    verify_password,
)
from app.models.user import User
from app.schemas.auth import LoginRequest, LoginResponse, RefreshRequest, TokenPair
from app.schemas.user import UserPublic, UserRegisterRequest


router = APIRouter(prefix="/api/auth", tags=["auth"])


@router.post(
    "/register",
    response_model=UserPublic,
    status_code=status.HTTP_201_CREATED,
    summary="註冊新帳號",
)
async def register(payload: UserRegisterRequest, db: DBSession) -> User:
    """註冊新帳號。

    規則:
      - email 必須全域唯一(case-insensitive)
      - 第一個成功註冊的人自動成為 owner,其他都是 member
    """
    email_lower = payload.email.lower()

    # 1. 檢查 email 是否已被使用
    existing_stmt = select(User).where(User.email == email_lower)
    existing = (await db.execute(existing_stmt)).scalar_one_or_none()
    if existing is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="此 email 已被註冊",
        )

    # 2. 第一個註冊者自動成為 owner
    count_stmt = select(func.count()).select_from(User)
    total_users = (await db.execute(count_stmt)).scalar_one()
    role = "owner" if total_users == 0 else "member"

    # 3. 建立使用者
    user = User(
        email=email_lower,
        password_hash=hash_password(payload.password),
        display_name=payload.display_name,
        role=role,
        is_active=True,
    )
    db.add(user)
    await db.commit()
    await db.refresh(user)
    return user


@router.post("/login", response_model=LoginResponse, summary="登入")
async def login(payload: LoginRequest, db: DBSession) -> LoginResponse:
    """登入,取得 access + refresh token。"""
    email_lower = payload.email.lower()

    stmt = select(User).where(User.email == email_lower, User.deleted_at.is_(None))
    user = (await db.execute(stmt)).scalar_one_or_none()

    # 故意把「找不到使用者」跟「密碼錯誤」回成同一個訊息,
    # 不讓攻擊者透過錯誤訊息列舉系統內有哪些 email
    if user is None or not verify_password(payload.password, user.password_hash):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="帳號或密碼錯誤",
        )

    if not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="帳號已停用,請聯絡管理員",
        )

    return LoginResponse(
        tokens=TokenPair(
            access_token=create_access_token(user.id),
            refresh_token=create_refresh_token(user.id),
        ),
        user=UserPublic.model_validate(user),
    )


@router.post("/refresh", response_model=TokenPair, summary="用 refresh token 換新的 access token")
async def refresh(payload: RefreshRequest, db: DBSession) -> TokenPair:
    """用 refresh token 換一組新的 access + refresh token。

    新拿一組 refresh token 是有意的(rotation):
    舊的 refresh token 應該由前端丟棄,降低被外洩重用的風險。
    """
    try:
        user_id = decode_token(payload.refresh_token, expected_type="refresh")
    except TokenError as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=str(e),
        ) from e

    # 確認使用者還在(沒被刪除、沒被停用)
    stmt = select(User).where(User.id == user_id, User.deleted_at.is_(None))
    user = (await db.execute(stmt)).scalar_one_or_none()
    if user is None or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="使用者不存在或已停用",
        )

    return TokenPair(
        access_token=create_access_token(user.id),
        refresh_token=create_refresh_token(user.id),
    )
