"""Saved plan model for plan persistence."""

from __future__ import annotations

import hashlib
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import BaseModel, Field
from pydantic import ValidationError as PydanticValidationError
from ruamel.yaml import YAML
from ruamel.yaml.error import YAMLError

from resume_as_code.models.errors import ValidationError

if TYPE_CHECKING:
    from resume_as_code.models.job_description import JobDescription
    from resume_as_code.services.ranker import RankingOutput


class SelectedWorkUnit(BaseModel):
    """A Work Unit selected in the plan."""

    id: str
    title: str
    score: float
    match_reasons: list[str] = Field(default_factory=list)


class SavedPlan(BaseModel):
    """Persisted plan for resume generation."""

    version: str = "1.0.0"
    created_at: datetime = Field(default_factory=datetime.now)

    # JD information
    jd_hash: str = Field(..., description="SHA256 hash of JD content")
    jd_title: str | None = None
    jd_path: str | None = None

    # Selections
    selected_work_units: list[SelectedWorkUnit]
    selection_count: int

    # Metadata
    top_k: int = 8
    ranker_version: str = "hybrid-rrf-v1"

    @classmethod
    def from_ranking(
        cls,
        ranking_output: RankingOutput,
        jd: JobDescription,
        jd_path: Path | None = None,
        top_k: int = 8,
    ) -> SavedPlan:
        """Create a SavedPlan from ranking output."""
        selected = ranking_output.results[:top_k]

        return cls(
            jd_hash=cls._hash_jd(jd.raw_text),
            jd_title=jd.title,
            jd_path=str(jd_path) if jd_path else None,
            selected_work_units=[
                SelectedWorkUnit(
                    id=r.work_unit_id,
                    title=r.work_unit.get("title", ""),
                    score=r.score,
                    match_reasons=r.match_reasons,
                )
                for r in selected
            ],
            selection_count=len(selected),
            top_k=top_k,
        )

    @staticmethod
    def _hash_jd(content: str) -> str:
        """Compute SHA256 hash of JD content (truncated to 16 chars)."""
        return hashlib.sha256(content.encode()).hexdigest()[:16]

    def save(self, path: Path) -> None:
        """Save plan to YAML file with human-readable header."""
        yaml = YAML()
        yaml.default_flow_style = False

        data = self.model_dump(mode="json")

        with open(path, "w", encoding="utf-8") as f:
            f.write("# Resume Plan - Generated by resume-as-code\n")
            f.write(f"# Created: {self.created_at.isoformat()}\n")
            f.write("# Use with: resume build --plan <this-file>\n\n")
            yaml.dump(data, f)

    @classmethod
    def load(cls, path: Path) -> SavedPlan:
        """Load plan from YAML file.

        Raises:
            ValidationError: If the file contains invalid YAML or missing required fields.
        """
        yaml = YAML()
        try:
            with open(path, encoding="utf-8") as f:
                data = yaml.load(f)
        except YAMLError as e:
            raise ValidationError(
                message=f"Invalid YAML in plan file: {e}",
                path=str(path),
                suggestion="Check the plan file for syntax errors",
            ) from e

        if data is None:
            raise ValidationError(
                message="Plan file is empty",
                path=str(path),
                suggestion="Regenerate the plan with: resume plan --jd <file> --output <plan.yaml>",
            )

        try:
            return cls.model_validate(data)
        except PydanticValidationError as e:
            raise ValidationError(
                message=f"Invalid plan file format: {e.error_count()} validation error(s)",
                path=str(path),
                suggestion=(
                    "Plan file may be corrupted. "
                    "Regenerate with: resume plan --jd <file> --output <plan.yaml>"
                ),
            ) from e
