import json
import logging
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from sqlalchemy.orm import Session
from app.config import settings
from app.models.database import get_db
from app.models.client import Client
from app.security.auth import (
    verify_api_key,
    create_access_token,
    hash_password,
    verify_password,
    create_session_token,
    create_purpose_token,
)
from app.security.dependencies import SESSION_COOKIE_NAME, get_session_client, decode_purpose_token
from app.security.rate_limit import limiter
from app.services.email import send_email
from app.schemas.api import (
    TokenRequest,
    TokenResponse,
    LoginRequest,
    SessionResponse,
    SetPasswordRequest,
    ForgotPasswordRequest,
    ChangePasswordRequest,
)

router = APIRouter(tags=["auth"])
logger = logging.getLogger(__name__)

SESSION_MAX_AGE_S = 7 * 24 * 60 * 60


def _issue_access_token(client: Client) -> TokenResponse:
    try:
        scopes = json.loads(client.scopes)
    except Exception:
        scopes = ["convert"]
    token = create_access_token(client_id=client.id, scopes=scopes)
    return TokenResponse(access_token=token, client_id=client.id)


@router.post("/auth/token", response_model=TokenResponse)
@limiter.limit("10/minute")
def get_token(request: Request, body: TokenRequest, db: Session = Depends(get_db)):
    clients = db.query(Client).filter(
        Client.active == True,  # noqa: E712
        Client.deleted_at == None,  # noqa: E711
    ).all()

    matched = None
    for c in clients:
        if verify_api_key(body.api_key, c.api_key_hash):
            matched = c
            break

    if not matched:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="API Key inválida")

    return _issue_access_token(matched)


def _set_session_cookie(response: Response, client_id: str) -> None:
    token = create_session_token(client_id)
    response.set_cookie(
        key=SESSION_COOKIE_NAME,
        value=token,
        httponly=True,
        secure=True,
        samesite="lax",
        max_age=SESSION_MAX_AGE_S,
        path="/",
    )


@router.post("/auth/login", response_model=SessionResponse)
@limiter.limit("5/minute")
def login(request: Request, body: LoginRequest, response: Response, db: Session = Depends(get_db)):
    client = db.query(Client).filter(
        Client.email == body.email,
        Client.active == True,  # noqa: E712
        Client.deleted_at == None,  # noqa: E711
    ).first()

    if not client or not client.password_hash or not verify_password(body.password, client.password_hash):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Correo o contraseña incorrectos")

    _set_session_cookie(response, client.id)
    return SessionResponse(client_id=client.id, name=client.name, email=client.email)


@router.post("/auth/logout", status_code=status.HTTP_204_NO_CONTENT)
def logout(response: Response):
    response.delete_cookie(SESSION_COOKIE_NAME, path="/")


@router.get("/auth/session", response_model=SessionResponse)
def session_info(client: Client = Depends(get_session_client)):
    return SessionResponse(client_id=client.id, name=client.name, email=client.email)


@router.post("/auth/token-from-session", response_model=TokenResponse)
def token_from_session(client: Client = Depends(get_session_client)):
    return _issue_access_token(client)


@router.post("/auth/set-password", response_model=SessionResponse)
@limiter.limit("10/minute")
def set_password(request: Request, body: SetPasswordRequest, response: Response, db: Session = Depends(get_db)):
    client_id = decode_purpose_token(body.token, "pwd_set")
    client = db.query(Client).filter(
        Client.id == client_id,
        Client.active == True,  # noqa: E712
        Client.deleted_at == None,  # noqa: E711
    ).first()
    if not client:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Cliente no encontrado")

    client.password_hash = hash_password(body.password)
    db.commit()

    _set_session_cookie(response, client.id)
    return SessionResponse(client_id=client.id, name=client.name, email=client.email)


@router.post("/auth/forgot-password", status_code=status.HTTP_204_NO_CONTENT)
@limiter.limit("5/minute")
def forgot_password(request: Request, body: ForgotPasswordRequest, db: Session = Depends(get_db)):
    client = db.query(Client).filter(
        Client.email == body.email,
        Client.active == True,  # noqa: E712
        Client.deleted_at == None,  # noqa: E711
    ).first()

    # Siempre responde 204 sin importar si el correo existe, para no revelar qué cuentas existen.
    if not client:
        return

    token = create_purpose_token(client.id, "pwd_set", expire_hours=2)
    link = f"https://{settings.PRIMARY_HOST or 'mt2mx.cl'}/cuenta?token={token}"
    try:
        send_email(
            to=client.email,
            subject="MT2MX — Restablecer tu contraseña",
            body=(
                f"Hola {client.name},\n\n"
                "Recibimos una solicitud para restablecer la contraseña de tu cuenta MT2MX.\n"
                f"Define una nueva contraseña aquí (válido por 2 horas):\n{link}\n\n"
                "Si no fuiste tú, puedes ignorar este correo.\n"
            ),
        )
    except Exception as e:
        logger.error(f"Fallo al enviar correo de recuperación: {e}", exc_info=True)


@router.post("/auth/change-password", status_code=status.HTTP_204_NO_CONTENT)
def change_password(
    body: ChangePasswordRequest,
    db: Session = Depends(get_db),
    client: Client = Depends(get_session_client),
):
    if not client.password_hash or not verify_password(body.current_password, client.password_hash):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Contraseña actual incorrecta")

    client.password_hash = hash_password(body.new_password)
    db.commit()
