"""
Auth API — password management endpoints.

Handles:
  POST /change-password           — update password (requires current password)
  POST /password-reset            — request a password reset email
  POST /password-reset/confirm    — confirm a password reset using the token
"""
from __future__ import annotations

import hashlib
import time
import uuid
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from config import settings
from db.models import User
from deps.auth import get_current_user
from deps.db import get_db
from deps.utils import get_client_ip
from services import auth as auth_service
from services.audit import write_audit_log
from api.auth.shared import (
    limiter,
    ChangePasswordRequest,
    PasswordResetRequest,
    PasswordResetConfirmRequest,
)

router = APIRouter()


# ── POST /change-password ─────────────────────────────────────────────────────

@router.post("/change-password")
async def change_password(
    request: Request,
    body: ChangePasswordRequest,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Update the current user's password.

    Checks:
      1. current_password matches stored hash
      2. new_password has not appeared in HIBP (SEC-03)
      3. new_password meets strength requirements (AUTH-01)
    """
    # Verify current password
    if not auth_service.verify_password(body.current_password, current_user.password_hash):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Current password is incorrect",
        )

    # HIBP breach check on new password (SEC-03)
    if await auth_service.check_hibp(body.new_password):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="This password has appeared in a data breach. Choose a different password.",
        )

    # Password strength check
    try:
        auth_service.validate_password_strength(body.new_password)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))

    # Update password
    _ip = get_client_ip(request)
    user = await session.get(User, current_user.id)
    user.password_hash = auth_service.hash_password(body.new_password)
    # Revoke other sessions; keep current one alive via skip_token_hash (CR-01)
    raw_cookie = request.cookies.get("refresh_token")
    skip_hash = hashlib.sha256(raw_cookie.encode()).hexdigest() if raw_cookie else None
    revoked = await auth_service.revoke_all_refresh_tokens(session, current_user.id, skip_token_hash=skip_hash)
    # D-13: password changed event (flush within same transaction before commit)
    await write_audit_log(
        session,
        event_type="auth.password_changed",
        user_id=current_user.id,
        actor_id=current_user.id,
        resource_id=None,
        ip_address=_ip,
        metadata_={"sessions_revoked": revoked},
    )
    # Revoke any pre-change access tokens still within their TTL window (T-7.2-01)
    await request.app.state.redis.set(
        f"user_nbf:{current_user.id}",
        int(time.time()),
        ex=settings.access_token_expire_minutes * 60,
    )
    await session.commit()

    return {"message": "Password updated", "sessions_revoked": revoked}


# ── POST /password-reset ──────────────────────────────────────────────────────

@router.post("/password-reset", status_code=status.HTTP_202_ACCEPTED)
@limiter.limit("5/hour")
async def password_reset_request(
    request: Request,
    body: PasswordResetRequest,
    session: AsyncSession = Depends(get_db),
):
    """Request a password reset email.

    Always returns 202 regardless of whether the email exists (anti-enumeration, T-02-22).
    If the user is found, a signed reset token (1-hour JWT) is generated and a Celery
    task is enqueued to send the email (D-02, D-03).
    """
    from sqlalchemy import select as _select  # noqa: PLC0415 (already imported above)

    result = await session.execute(_select(User).where(User.email == str(body.email)))
    user: Optional[User] = result.scalar_one_or_none()

    if user is not None:
        token = auth_service.create_password_reset_token(str(user.id))
        reset_link = f"{settings.frontend_url}/password-reset/confirm?token={token}"
        # Deferred import to avoid circular import; Celery task is fire-and-forget
        from tasks.email_tasks import send_reset_email  # noqa: PLC0415
        send_reset_email.delay(user.email, reset_link)

    # Always return 202 (anti-enumeration — never reveal whether email exists)
    return {
        "message": (
            "If an account exists for that email, you will receive a reset link shortly."
        )
    }


# ── POST /password-reset/confirm ──────────────────────────────────────────────

@router.post("/password-reset/confirm")
async def password_reset_confirm(
    request: Request,
    body: PasswordResetConfirmRequest,
    session: AsyncSession = Depends(get_db),
):
    """Confirm a password reset using the token from the email link.

    Validates the reset token, enforces password strength + HIBP check, updates
    the password, and revokes all refresh tokens. Does NOT issue new tokens —
    the user must sign in again through /login (AUTH-05, T-02-21).
    """
    try:
        user_id_str = auth_service.decode_password_reset_token(body.token)
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired reset link",
        )

    # Password strength validation
    try:
        auth_service.validate_password_strength(body.new_password)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc))

    # HIBP breach check (SEC-03)
    if await auth_service.check_hibp(body.new_password):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="This password has appeared in a data breach. Choose a different password.",
        )

    # Load user
    user = await session.get(User, uuid.UUID(user_id_str))
    if user is None or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired reset link",
        )

    # Update password and revoke all sessions (forces re-auth through TOTP if enabled)
    user.password_hash = auth_service.hash_password(body.new_password)
    await auth_service.revoke_all_refresh_tokens(session, user.id)
    # Revoke any pre-reset access tokens still within their TTL window (T-7.2-01)
    await request.app.state.redis.set(
        f"user_nbf:{user.id}",
        int(time.time()),
        ex=settings.access_token_expire_minutes * 60,
    )
    await session.commit()

    # Do NOT issue tokens (AUTH-05 — user must pass TOTP gate on next login)
    return {"message": "Password updated. Please sign in."}
