"""Роутер аутентификации: регистрация, подтверждение email, JWT access/refresh, logout."""

from typing import Annotated

from fastapi import APIRouter, Cookie, Depends, HTTPException, Response, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession

from core.config import get_settings
from core.db import get_session
from core.redis import redis_client
from models.user import User
from repositories.admin import TeamRepository
from schemas.auth import (
    RegisterIn,
    RegistrationOptionsOut,
    RegistrationTeamOptionOut,
    TokenOut,
    UserOut,
    VerifyEmailIn,
)
from services.auth import (
    AuthService,
    ConsentRequiredError,
    EmailAlreadyRegisteredError,
    EmailNotVerifiedError,
    InvalidCredentialsError,
    InvalidEmailDomainError,
    InvalidRefreshTokenError,
    InvalidTeamSelectionError,
    InvalidVerificationTokenError,
)
from services.email import create_email_backend
from services.instance_settings import InstanceSettingsService

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

REFRESH_COOKIE_NAME = "refresh_token"
REFRESH_COOKIE_PATH = "/api/v1/auth"


def get_auth_service(session: Annotated[AsyncSession, Depends(get_session)]) -> AuthService:
    """Собрать `AuthService` с реальными зависимостями (БД, Redis, email-бэкенд из настроек)."""
    return AuthService(
        session=session, redis=redis_client, email_backend=create_email_backend(get_settings())
    )


@router.get("/registration-options", response_model=RegistrationOptionsOut)
async def registration_options(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> RegistrationOptionsOut:
    """Публичные опции карточки регистрации: выбор команды и верификация домена email.

    Список команд отдаётся только при включённой настройке инстанса
    `registration_team_choice` — иначе пустой массив (справочник команд не
    раскрывается, пока выбор выключен). `email_domains` — эталонные домены при
    включённой настройке `registration_email_domain`, иначе пустой список.
    """
    cfg = await InstanceSettingsService(session).get()
    teams: list[RegistrationTeamOptionOut] = []
    if cfg.registration_team_choice:
        items, _ = await TeamRepository(session).list_all()
        teams = [RegistrationTeamOptionOut(id=team.id, name=team.name) for team in items]
    email_domains = cfg.registration_email_domains if cfg.registration_email_domain_enabled else []
    return RegistrationOptionsOut(
        team_choice_enabled=cfg.registration_team_choice,
        teams=teams,
        email_domains=email_domains,
        consent_required=cfg.consent_required,
        consent_text=cfg.consent_policy_text,
        consent_version=cfg.consent_policy_version,
    )


@router.post("/register", status_code=status.HTTP_201_CREATED, response_model=UserOut)
async def register(
    data: RegisterIn, service: Annotated[AuthService, Depends(get_auth_service)]
) -> User:
    """Зарегистрировать нового пользователя и отправить письмо для подтверждения email."""
    try:
        return await service.register(
            email=data.email,
            name_user=data.name_user,
            password=data.password,
            team_id=data.team_id,
            consent_accepted=data.consent_accepted,
        )
    except EmailAlreadyRegisteredError as exc:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT, detail="email_already_registered"
        ) from exc
    except InvalidTeamSelectionError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_team_selection"
        ) from exc
    except InvalidEmailDomainError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_email_domain"
        ) from exc
    except ConsentRequiredError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="consent_required"
        ) from exc


@router.post("/verify-email", status_code=status.HTTP_204_NO_CONTENT)
async def verify_email(
    data: VerifyEmailIn, service: Annotated[AuthService, Depends(get_auth_service)]
) -> None:
    """Подтвердить email по токену, полученному в письме."""
    try:
        await service.verify_email(data.token)
    except InvalidVerificationTokenError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_or_expired_token"
        ) from exc


@router.post("/token", response_model=TokenOut)
async def login(
    response: Response,
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    service: Annotated[AuthService, Depends(get_auth_service)],
) -> TokenOut:
    """OAuth2 password flow: вход по email (передаётся как `username`) и паролю."""
    try:
        pair = await service.login(email=form_data.username, password=form_data.password)
    except InvalidCredentialsError as exc:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid_credentials"
        ) from exc
    except EmailNotVerifiedError as exc:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="email_not_verified"
        ) from exc

    _set_refresh_cookie(response, pair.refresh_token)
    return TokenOut(access_token=pair.access_token)


@router.post("/refresh", response_model=TokenOut)
async def refresh(
    response: Response,
    service: Annotated[AuthService, Depends(get_auth_service)],
    refresh_token: Annotated[str | None, Cookie(alias=REFRESH_COOKIE_NAME)] = None,
) -> TokenOut:
    """Ротировать refresh-токен из cookie и выдать новый access-токен."""
    if refresh_token is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED, detail="missing_refresh_token"
        )
    try:
        pair = await service.refresh(refresh_token)
    except InvalidRefreshTokenError as exc:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid_refresh_token"
        ) from exc

    _set_refresh_cookie(response, pair.refresh_token)
    return TokenOut(access_token=pair.access_token)


@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
async def logout(
    response: Response,
    service: Annotated[AuthService, Depends(get_auth_service)],
    refresh_token: Annotated[str | None, Cookie(alias=REFRESH_COOKIE_NAME)] = None,
) -> None:
    """Отозвать refresh-токен (удалить из Redis) и погасить cookie."""
    if refresh_token is not None:
        await service.logout(refresh_token)
    settings = get_settings()
    response.delete_cookie(
        REFRESH_COOKIE_NAME,
        path=REFRESH_COOKIE_PATH,
        secure=settings.auth_cookie_secure,
        httponly=True,
        samesite="strict",
    )


def _set_refresh_cookie(response: Response, refresh_token: str) -> None:
    """Установить httpOnly SameSite=Strict cookie с refresh-токеном.

    Флаг `Secure` управляется настройкой `auth_cookie_secure` — в dev по
    `http://localhost` его нужно отключать (см. `core/config.py`), т.к.
    Safari (в отличие от Chrome) не сохраняет Secure-cookie без HTTPS.
    """
    settings = get_settings()
    response.set_cookie(
        key=REFRESH_COOKIE_NAME,
        value=refresh_token,
        httponly=True,
        secure=settings.auth_cookie_secure,
        samesite="strict",
        path=REFRESH_COOKIE_PATH,
        max_age=settings.refresh_token_ttl_days * 24 * 3600,
    )
