"""Local email/password auth endpoints.

Lives alongside the OIDC/Atoms platform auth in ``routers/auth.py``.
All endpoints return an application JWT compatible with the existing
``get_current_user`` dependency, so admin routes keep working.

When SMS MFA is enabled for the user (or globally enforced for admins),
``/login`` returns a short-lived ``mfa_token`` instead of a session token.
The frontend must then post that token + the SMS code to
``/api/v1/auth/mfa/challenge/verify`` to obtain the real session JWT.
"""

from __future__ import annotations

import logging
from typing import Optional

from core.database import get_db
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from schemas.local_auth import LocalAuthResponse, LocalLoginRequest, LocalRegisterRequest
from services.audit_log import ACTION_LOGIN, record_admin_event
from services.auth import AuthService
from services.local_auth import LocalAuthService
from services.mfa import (
    MFA_CHALLENGE_TTL_MINUTES,
    get_mfa,
    issue_mfa_challenge_token,
    send_login_challenge,
    should_require_mfa,
)
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

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


class LocalLoginMfaRequiredResponse(BaseModel):
    """Returned by ``/login`` when MFA is required before issuing a session."""

    mfa_required: bool = True
    mfa_token: str
    phone_masked: Optional[str] = None
    expires_in_seconds: int = MFA_CHALLENGE_TTL_MINUTES * 60
    message: str = "A verification code has been sent to your registered phone."


@router.post("/register", response_model=LocalAuthResponse, status_code=status.HTTP_201_CREATED)
async def register_local_user(payload: LocalRegisterRequest, db: AsyncSession = Depends(get_db)) -> LocalAuthResponse:
    """Create a new local email/password account and return an app JWT."""
    local_service = LocalAuthService(db)
    try:
        user = await local_service.register(
            email=str(payload.email),
            password=payload.password,
            name=payload.name,
            role="user",
        )
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
    except Exception:  # noqa: BLE001
        logger.exception("[local_auth] Registration failed for email=%s", payload.email)
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Registration failed")

    auth_service = AuthService(db)
    token, _expires_at, _claims = await auth_service.issue_app_token(user=user)
    return LocalAuthResponse(
        token=token,
        user_id=user.id,
        email=user.email,
        role=user.role,
    )


@router.post("/login")
async def login_local_user(
    payload: LocalLoginRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """Verify email/password. If MFA is required, return an mfa_token challenge;
    otherwise return a full session JWT."""
    local_service = LocalAuthService(db)
    user = await local_service.authenticate(email=str(payload.email), password=payload.password)
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password")

    mfa_row = await get_mfa(db, user.id)
    if await should_require_mfa(user, mfa_row):
        if mfa_row is None or not mfa_row.enabled or not mfa_row.phone_e164:
            # Admin-enforcement is on but this user has not enrolled yet.
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=(
                    "Multi-factor authentication is required for this account but no phone is "
                    "enrolled. Please contact an administrator to complete MFA setup."
                ),
            )
        try:
            updated = await send_login_challenge(db, user)
        except ValueError as exc:
            raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=str(exc))
        except RuntimeError as exc:
            logger.warning("[local_auth] Failed to dispatch MFA SMS for user=%s: %s", user.id, exc)
            raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc))

        mfa_token = issue_mfa_challenge_token(user.id)
        phone_masked = None
        if updated.phone_e164 and len(updated.phone_e164) > 4:
            phone_masked = f"{updated.phone_e164[:3]}•••{updated.phone_e164[-2:]}"
        return LocalLoginMfaRequiredResponse(
            mfa_required=True,
            mfa_token=mfa_token,
            phone_masked=phone_masked,
        )

    # No MFA required — issue the session token immediately.
    auth_service = AuthService(db)
    token, _expires_at, _claims = await auth_service.issue_app_token(user=user)

    await record_admin_event(
        db,
        action=ACTION_LOGIN,
        admin_user_id=user.id,
        admin_email=user.email,
        target_user_id=user.id,
        target_email=user.email,
        details={"role": user.role, "auth_provider": "local"},
        request=request,
    )

    return LocalAuthResponse(
        token=token,
        user_id=user.id,
        email=user.email,
        role=user.role,
    )