from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from app.core.config import settings
from app.core.datetime_utils import ensure_utc, utc_now
from app.core.redis import check_rate_limit, client_ip
from app.modules.auth.schemas import (
    ForgotPasswordIn,
    LoginIn,
    LoginOut,
    RegisterIn,
    ResendVerificationIn,
    ResetPasswordIn,
    VerifyEmailIn,
)
from app.modules.auth.service import (
    forgot_password,
    login,
    logout,
    refresh,
    register,
    resend_verification,
    reset_password,
    verify_email_token,
)
from app.modules.users import repository

router = APIRouter()
optional_bearer = HTTPBearer(auto_error=False)


def _allowed_origins() -> set[str]:
    origins: set[str] = set()
    for raw in (settings.frontend_url, settings.public_base_url, *settings.cors_origins):
        if not raw:
            continue
        normalized = raw.rstrip("/")
        origins.add(normalized)
        if "://localhost" in normalized:
            origins.add(normalized.replace("://localhost", "://127.0.0.1"))
        if "://127.0.0.1" in normalized:
            origins.add(normalized.replace("://127.0.0.1", "://localhost"))
    return origins


def _enforce_origin(request: Request, *, require_header: bool = False) -> None:
    allowed = _allowed_origins()
    origin = (request.headers.get("Origin") or "").rstrip("/")
    referer = request.headers.get("Referer") or ""
    if require_header and not origin and not referer:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="INVALID_ORIGIN")
    if origin and origin not in allowed:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="INVALID_ORIGIN")
    if not origin and referer:
        if not any(referer.startswith(base) for base in allowed):
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="INVALID_ORIGIN")


def _set_refresh_cookie(response: Response, refresh_token: str) -> None:
    response.set_cookie(
        key="refresh_token",
        value=refresh_token,
        httponly=True,
        secure=settings.cookie_secure,
        samesite="lax",
        path="/api/v1/auth",
        max_age=settings.jwt_refresh_ttl_days * 24 * 60 * 60,
    )


@router.post("/register")
async def register_route(payload: RegisterIn, request: Request):
    _enforce_origin(request)
    check_rate_limit(f"register:{client_ip(request)}", limit=3, window_seconds=3600)
    user = register(payload.email, payload.password)
    _ = user
    return {"message": "If email is valid, verification has been sent."}


@router.post("/verify-email")
async def verify_email_route(payload: VerifyEmailIn):
    try:
        user = verify_email_token(payload.token)
    except ValueError as exc:
        detail = str(exc)
        if detail == "TOKEN_EXPIRED":
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="TOKEN_EXPIRED")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="INVALID_TOKEN")
    _ = user
    return {"status": "active"}


@router.post("/login", response_model=LoginOut)
async def login_route(payload: LoginIn, request: Request, response: Response):
    _enforce_origin(request)
    user = repository.get_user_by_email(payload.email)
    if user:
        locked_until = ensure_utc(user.locked_until)
        if locked_until and locked_until > utc_now():
            raise HTTPException(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                detail="ACCOUNT_TEMPORARILY_LOCKED",
            )
    check_rate_limit(
        f"login:{client_ip(request)}:{payload.email.lower()}",
        limit=5,
        window_seconds=60,
    )
    try:
        access_token, refresh_token, user = login(payload.email, payload.password)
    except ValueError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="INVALID_CREDENTIALS")
    except PermissionError as exc:
        detail = str(exc)
        if detail == "EMAIL_NOT_VERIFIED":
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="EMAIL_NOT_VERIFIED")
        if detail == "ACCOUNT_TEMPORARILY_LOCKED":
            raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="ACCOUNT_TEMPORARILY_LOCKED")
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ACCOUNT_BLOCKED")

    _set_refresh_cookie(response, refresh_token)
    return {
        "access_token": access_token,
        "expires_in": settings.jwt_access_ttl_min * 60,
        "user": {
            "id": user.id,
            "email": user.email,
            "role": user.role,
            "is_superuser": user.is_superuser,
            "status": user.status,
        },
    }


@router.post("/refresh", response_model=LoginOut)
async def refresh_route(request: Request, response: Response):
    refresh_token = request.cookies.get("refresh_token")
    if not refresh_token:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="REFRESH_MISSING")
    _enforce_origin(request, require_header=True)
    check_rate_limit(f"refresh:{client_ip(request)}", limit=30, window_seconds=60)
    try:
        access_token, new_refresh, user = refresh(refresh_token)
    except PermissionError as exc:
        detail = str(exc)
        if detail == "EMAIL_NOT_VERIFIED":
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="EMAIL_NOT_VERIFIED")
        if detail == "ACCOUNT_BLOCKED":
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ACCOUNT_BLOCKED")
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="INVALID_REFRESH")
    _set_refresh_cookie(response, new_refresh)
    return {
        "access_token": access_token,
        "expires_in": settings.jwt_access_ttl_min * 60,
        "user": {
            "id": user.id,
            "email": user.email,
            "role": user.role,
            "is_superuser": user.is_superuser,
            "status": user.status,
        },
    }


@router.post("/logout")
async def logout_route(
    request: Request,
    response: Response,
    credentials: HTTPAuthorizationCredentials | None = Depends(optional_bearer),
):
    _enforce_origin(request, require_header=True)
    refresh_token = request.cookies.get("refresh_token")
    access_token = credentials.credentials if credentials else None
    logout(refresh_token, access_token)
    response.delete_cookie(
        "refresh_token",
        path="/api/v1/auth",
        secure=settings.cookie_secure,
        samesite="lax",
    )
    return {"message": "logged_out"}


@router.post("/forgot-password")
async def forgot_password_route(payload: ForgotPasswordIn, request: Request):
    _enforce_origin(request)
    check_rate_limit(
        f"forgot:{client_ip(request)}:{payload.email.lower()}",
        limit=3,
        window_seconds=3600,
    )
    forgot_password(payload.email)
    return {"message": "If email is registered, reset instructions have been sent."}


@router.post("/reset-password")
async def reset_password_route(payload: ResetPasswordIn):
    try:
        reset_password(payload.token, payload.new_password)
    except ValueError as exc:
        detail = str(exc)
        if detail == "TOKEN_EXPIRED":
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="TOKEN_EXPIRED")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="INVALID_TOKEN")
    return {"message": "password_updated"}


@router.post("/resend-verification")
async def resend_verification_route(payload: ResendVerificationIn, request: Request):
    _enforce_origin(request)
    check_rate_limit(
        f"resend:{client_ip(request)}:{payload.email.lower()}",
        limit=3,
        window_seconds=3600,
    )
    resend_verification(payload.email)
    return {"message": "If email is registered, verification has been sent."}
