"""Forgot-password flow for local email/password accounts.

Two endpoints:

- ``POST /api/v1/auth/local/forgot-password`` — always returns 200 regardless
  of whether the email exists, to avoid leaking which addresses have local
  accounts. If the address matches a local user, a reset email is dispatched
  via the configured SMTP settings.
- ``POST /api/v1/auth/local/reset-password`` — consumes the one-time token
  and sets a new password.
"""

from __future__ import annotations

import logging
import os

from core.database import get_db
from fastapi import APIRouter, Depends, HTTPException, Request, status
from schemas.admin_users import (
    ForgotPasswordRequest,
    ResetPasswordConfirmRequest,
    SimpleMessageResponse,
)
from services.audit_log import ACTION_PASSWORD_RESET_COMPLETED, record_admin_event
from services.email_sender import SmtpConfigError, send_email
from services.local_auth import LocalAuthService
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

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


def _frontend_base_url(request: Request) -> str:
    """Pick a best-effort public base URL for the reset link."""
    env_url = os.getenv("FRONTEND_BASE_URL") or os.getenv("FRONTEND_URL") or os.getenv("SITE_URL")
    if env_url:
        return env_url.rstrip("/")
    # Fall back to the Origin/Referer header, then to the request's own scheme+host.
    origin = request.headers.get("origin") or request.headers.get("referer")
    if origin:
        # Strip any path/query from referer
        from urllib.parse import urlparse

        parsed = urlparse(origin)
        if parsed.scheme and parsed.netloc:
            return f"{parsed.scheme}://{parsed.netloc}"
    return str(request.base_url).rstrip("/")


def _render_reset_email(reset_url: str, recipient_name: str | None) -> tuple[str, str]:
    """Return (text_body, html_body) for the password-reset email."""
    greeting = f"Hi {recipient_name}," if recipient_name else "Hello,"
    text = (
        f"{greeting}\n\n"
        "We received a request to reset the password on your DoItEasy admin account.\n\n"
        f"Click or paste the following link into your browser to choose a new password:\n{reset_url}\n\n"
        "This link will expire in 60 minutes and can only be used once. "
        "If you did not request a password reset, you can safely ignore this email.\n\n"
        "— DoItEasy Security Team"
    )
    html = f"""
      <div style=\"font-family: Arial, sans-serif; color: #0f1f17; max-width: 520px; margin: auto;\">
        <p>{greeting}</p>
        <p>We received a request to reset the password on your <strong>DoItEasy</strong> admin account.</p>
        <p>
          <a href=\"{reset_url}\"
             style=\"display:inline-block;background:#0f5132;color:#ffffff;text-decoration:none;
                    padding:12px 20px;border-radius:8px;font-weight:600;\">
            Reset my password
          </a>
        </p>
        <p style=\"font-size:13px;color:#555;\">
          Or copy and paste this URL into your browser:<br/>
          <span style=\"word-break:break-all;color:#0f5132;\">{reset_url}</span>
        </p>
        <p style=\"font-size:13px;color:#555;\">
          This link expires in <strong>60 minutes</strong> and can only be used once.
          If you did not request a password reset, you can safely ignore this email.
        </p>
        <p style=\"font-size:12px;color:#888;margin-top:24px;\">— DoItEasy Security Team</p>
      </div>
    """
    return text, html


@router.post("/forgot-password", response_model=SimpleMessageResponse)
async def request_password_reset(
    payload: ForgotPasswordRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> SimpleMessageResponse:
    """Email a one-time reset link to the user, if they have a local account.

    Always returns 200 to avoid user-enumeration oracles.
    """
    generic = SimpleMessageResponse(
        detail="If an account with that email exists, a password-reset link has been sent."
    )
    service = LocalAuthService(db)
    user = await service.get_user_by_email(str(payload.email))
    if user is None:
        logger.info(
            "[password_reset] Reset requested for unknown email=%s", payload.email
        )
        return generic
    profile = await service.get_profile_for_user(user)
    if profile is None or profile.auth_provider != "local":
        logger.info(
            "[password_reset] Reset requested for non-local email=%s", payload.email
        )
        return generic
    if profile.is_active is False:
        logger.info("[password_reset] Reset requested for disabled account=%s", user.email)
        return generic

    token, _expires_at = await service.create_reset_token(user)
    base_url = _frontend_base_url(request)
    reset_url = f"{base_url}/admin/reset-password?token={token}"

    text_body, html_body = _render_reset_email(reset_url=reset_url, recipient_name=user.name)
    try:
        await send_email(
            to_addrs=[user.email],
            subject="Reset your DoItEasy admin password",
            text_body=text_body,
            html_body=html_body,
        )
    except SmtpConfigError as exc:
        logger.error("[password_reset] SMTP not configured: %s", exc)
        # Surface a proper error to the admin so they can fix SMTP settings.
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Email delivery is not configured. Please contact an administrator.",
        )
    except Exception:  # noqa: BLE001
        logger.exception("[password_reset] Failed to send reset email to %s", user.email)
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Could not send reset email. Please try again later.",
        )

    return generic


@router.post("/reset-password", response_model=SimpleMessageResponse)
async def confirm_password_reset(
    payload: ResetPasswordConfirmRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> SimpleMessageResponse:
    """Consume a reset token and set a new password."""
    service = LocalAuthService(db)
    try:
        user = await service.consume_reset_token(payload.token, payload.new_password)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))

    # Self-service event: the actor IS the target.
    await record_admin_event(
        db,
        action=ACTION_PASSWORD_RESET_COMPLETED,
        admin_user_id=user.id,
        admin_email=user.email,
        target_user_id=user.id,
        target_email=user.email,
        details={"flow": "forgot_password"},
        request=request,
    )
    return SimpleMessageResponse(detail="Password has been reset. You can now log in.")