"""
CV service. Stateless helpers for fetching and assembling CV responses.

These helpers do NO authorization. The caller (route handler) is responsible
for verifying that the requested user_id is allowed (own user, or admin).
"""
import asyncio
import logging
from typing import Optional

from fastapi import HTTPException, status

from app.core.config import settings
from app.core.supabase_client import supabase
from app.schemas.cv import (
    CVPayloadInput,
    CVResponse,
    CVOutput,
    PersonalInfo,
    ExperienceInput,
    FormationInput,
    LanguageInput,
)
from app.services.pdf_service import pdf_service
from app.services.storage_service import storage_service
from app.utils.date_formatter import format_date_range

logger = logging.getLogger(__name__)


def _build_personal_info(profile: dict, email: str) -> PersonalInfo:
    """Construct PersonalInfo from profile row + fresh email."""
    firstname = (profile.get("firstname") or "").strip()
    lastname = (profile.get("lastname") or "").strip()
    full_name = f"{firstname} {lastname}".strip() or "Candidat"

    return PersonalInfo(
        full_name=full_name,
        job_title=profile.get("profession") or "",
        phone=profile.get("phone") or "",
        email=email,
        address=profile.get("city") or "",
    )



def _fetch_cv_output(cv_id: str) -> CVOutput:
    """
    Re-fetch the just-UPSERTED CV row from public.cvs and map it to CVOutput.
    Database is the single source of truth — guarantees the response
    reflects exactly what is persisted (tolerates BDD triggers, normalizations,
    timestamps, etc.).

    Raises:
        HTTPException(500) if the row cannot be re-read or mapping fails.
    """
    try:
        resp = (
            supabase.table("cvs")
            .select("personal_info,profil,experiences,formations,skills,languages")
            .eq("id", cv_id)
            .single()
            .execute()
        )
    except Exception as exc:
        logger.exception("Failed to re-fetch cv row (cv_id=%s) for CVOutput", cv_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="CV saved but could not be retrieved for response.",
        ) from exc

    row = resp.data
    if not row:
        logger.error("CV row missing after UPSERT (cv_id=%s)", cv_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="CV saved but disappeared on re-read.",
        )

    try:
        return CVOutput(
            personal_info=PersonalInfo(**row["personal_info"]),
            profil=row["profil"] or "",
            experiences=[ExperienceInput(**e) for e in (row["experiences"] or [])],
            formations=[FormationInput(**f) for f in (row["formations"] or [])],
            skills=list(row["skills"] or []),
            languages=[LanguageInput(**l) for l in (row["languages"] or [])],
        )
    except Exception as exc:
        logger.exception("Failed to map cvs row to CVOutput (cv_id=%s)", cv_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="CV saved but response mapping failed.",
        ) from exc


def _prepare_template_data(payload: CVPayloadInput, personal_info: PersonalInfo) -> dict:
    """Build the dict shape expected by the Jinja2 CV template."""
    experiences = [
        {
            "position": e.position,
            "company": e.company,
            "description": e.description,
            "date_range": format_date_range(
                e.year_start, e.month_start, e.year_end, e.month_end, e.is_present
            ),
        }
        for e in payload.experiences
    ]

    formations = [
        {
            "degree": f.degree,
            "institution": f.institution,
            "date_range": format_date_range(
                f.year_start, f.month_start, f.year_end, f.month_end, f.is_present
            ),
        }
        for f in payload.formations
    ]

    return {
        "personal_info": personal_info.model_dump(),
        "profil": payload.profil,
        "experiences": experiences,
        "formations": formations,
        "skills": payload.skills,
        "languages": [lang.model_dump() for lang in payload.languages],
    }


def _serialize_jsonb(items: list) -> list:
    """Pydantic models -> list of dicts for jsonb columns."""
    return [item.model_dump() for item in items]


def fetch_cv_by_user(user_id: str) -> Optional[dict]:
    """
    Fetch the persisted CV for a given user.

    Returns a dict with keys:
      - cv_id: str (UUID)
      - storage_path: Optional[str] (e.g. "{user_id}/cv.pdf", or None if PDF gen never succeeded)
      - cv_output: CVOutput

    Returns None if the user has no CV row yet (not an error — first-time user
    or empty state).

    Raises:
        HTTPException(500) if the row exists but mapping fails (corrupted data).
    """
    try:
        resp = (
            supabase.table("cvs")
            .select("id,pdf_url,personal_info,profil,experiences,formations,skills,languages")
            .eq("user_id", user_id)
            .limit(1)
            .execute()
        )
    except Exception as exc:
        logger.exception("Failed to fetch cv row for user=%s", user_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Could not retrieve CV.",
        ) from exc

    rows = resp.data or []
    if not rows:
        return None

    row = rows[0]

    try:
        cv_output = CVOutput(
            personal_info=row["personal_info"],
            profil=row["profil"],
            experiences=row["experiences"],
            formations=row["formations"],
            skills=row["skills"],
            languages=row["languages"],
        )
    except Exception as exc:
        logger.exception("Failed to map cvs row to CVOutput (user=%s)", user_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="CV data is corrupted.",
        ) from exc

    return {
        "cv_id": row["id"],
        "storage_path": row.get("pdf_url"),
        "cv_output": cv_output,
    }


def build_cv_response(user_id: str) -> Optional[CVResponse]:
    """
    Assemble a CVResponse for the given user, including a fresh signed URL.

    Returns:
        - None if the user has no CV row (caller should respond 404).
        - CVResponse with pdf_url + warning fields set appropriately:
            * pdf_url=None + warning if storage_path missing or signed URL gen fails
            * pdf_url=signed_url + no warning if all good

    The caller is responsible for the HTTP status code mapping (404 vs 200).
    """
    cv_row = fetch_cv_by_user(user_id)
    if cv_row is None:
        return None

    cv_id = cv_row["cv_id"]
    storage_path = cv_row["storage_path"]
    cv_output = cv_row["cv_output"]

    # If no storage_path persisted, the PDF generation/upload failed previously.
    # Return the CV data anyway with a warning — frontend can still render the preview.
    if not storage_path:
        logger.warning("CV exists but no storage_path (user=%s, cv_id=%s)", user_id, cv_id)
        return CVResponse(
            cv_id=cv_id,
            pdf_url=None,
            cv=cv_output,
            warning="PDF non disponible. Régénérez votre CV.",
        )

    # Generate a fresh signed URL (the persisted one — if any — has expired).
    try:
        pdf_url = storage_service.get_signed_url(
            bucket=settings.SUPABASE_CVS_BUCKET,
            path=storage_path,
        )
    except Exception:
        logger.exception("Signed URL generation failed (user=%s, cv_id=%s)", user_id, cv_id)
        return CVResponse(
            cv_id=cv_id,
            pdf_url=None,
            cv=cv_output,
            warning="CV récupéré mais l'URL du PDF n'a pas pu être générée.",
        )

    return CVResponse(
        cv_id=cv_id,
        pdf_url=pdf_url,
        cv=cv_output,
        warning=None,
    )


async def save_cv_for_user(user_id: str, payload: CVPayloadInput) -> CVResponse:
    """
    Create or update the CV for `user_id` and (re)generate its PDF.

    Shared by POST /cvs/me (candidate, user_id from JWT) and
    PATCH /admin/users/{user_id}/cv (admin, user_id from URL path).
    """
    # NOTE: le fetch du profil ci-dessous sert AUSSI de vérification que
    # user_id correspond à un profil existant (404 sinon). Ne pas supprimer
    # ou déplacer cette étape sans ajouter un check d'existence dédié —
    # c'est le seul garde-fou actuel contre un user_id invalide (cf. audit
    # du 2026-07-06, section 7 : gap identifié).
    # === 1. Fetch profile from public.profiles ===
    try:
        profile_resp = (
            supabase.table("profiles")
            .select("firstname,lastname,phone,profession,city")
            .eq("id", user_id)
            .single()
            .execute()
        )
        profile = profile_resp.data
    except Exception as exc:
        logger.exception("Failed to fetch profile for user=%s", user_id)
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Profile not found. Please complete your profile before generating a CV.",
        ) from exc

    if not profile:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Profile not found. Please complete your profile before generating a CV.",
        )

    # === 2. Fetch fresh email from auth.users (decision D3 = B) ===
    try:
        user_resp = supabase.auth.admin.get_user_by_id(user_id)
        email = user_resp.user.email or ""
    except Exception as exc:
        logger.exception("Failed to fetch auth email for user=%s", user_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Could not retrieve user email.",
        ) from exc

    # === 3. Build PersonalInfo backend-side ===
    personal_info = _build_personal_info(profile, email)

    # === 4. UPSERT CV row in public.cvs ===
    cv_row = {
        "user_id": user_id,
        "personal_info": personal_info.model_dump(),
        "profil": payload.profil,
        "experiences": _serialize_jsonb(payload.experiences),
        "formations": _serialize_jsonb(payload.formations),
        "skills": payload.skills,
        "languages": _serialize_jsonb(payload.languages),
    }

    try:
        upsert_resp = (
            supabase.table("cvs")
            .upsert(cv_row, on_conflict="user_id")
            .execute()
        )
        if not upsert_resp.data:
            raise RuntimeError(f"UPSERT returned no data: {upsert_resp}")
        cv_id = upsert_resp.data[0]["id"]
    except Exception as exc:
        logger.exception("Failed to UPSERT cv for user=%s", user_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to save CV.",
        ) from exc

    logger.info("CV upserted (cv_id=%s, user=%s)", cv_id, user_id)

    # === 5. Generate PDF (CPU-bound, run in thread) ===
    try:
        template_data = _prepare_template_data(payload, personal_info)
        pdf_bytes = await asyncio.to_thread(
            pdf_service.generate_cv_pdf, template_data
        )
    except Exception as exc:
        logger.exception("PDF generation failed for user=%s", user_id)
        # Best-effort (Q5 decision B): CV is saved, return null pdfUrl + warning
        return CVResponse(
            cv_id=cv_id,
            pdf_url=None,
            cv=_fetch_cv_output(cv_id),
            warning="CV saved but PDF generation failed. Please retry.",
        )

    # === 6. Upload PDF to Storage (best-effort) ===
    storage_path = await asyncio.to_thread(
        storage_service.upload_cv_pdf, user_id, pdf_bytes
    )
    if storage_path is None:
        logger.warning("Storage upload failed for user=%s — returning null pdfUrl", user_id)
        return CVResponse(
            cv_id=cv_id,
            pdf_url=None,
            cv=_fetch_cv_output(cv_id),
            warning="CV saved but PDF storage failed. Please retry.",
        )

    # === 7. Generate signed URL ===
    try:
        pdf_url = storage_service.get_signed_url(
            bucket=settings.SUPABASE_CVS_BUCKET,
            path=storage_path,
        )
    except Exception as exc:
        logger.exception("Signed URL generation failed for user=%s", user_id)
        return CVResponse(
            cv_id=cv_id,
            pdf_url=None,
            cv=_fetch_cv_output(cv_id),
            warning="CV saved and PDF uploaded but URL generation failed.",
        )

    # === 8. Update cvs.pdf_url for future reference ===
    try:
        supabase.table("cvs").update({"pdf_url": storage_path}).eq("id", cv_id).execute()
    except Exception as exc:
        logger.warning("Failed to persist pdf_url in cvs row (non-fatal): %s", exc)

    return CVResponse(
        cv_id=cv_id,
        pdf_url=pdf_url,
        cv=_fetch_cv_output(cv_id),
        warning=None,
    )
