"""Core CV tailoring engine — matches master CV content to a job posting using AI."""

import json
import re
import subprocess
from pathlib import Path

from anthropic import Anthropic

from .parser import get_bullet_variants, get_skill_groups, parse_master

SYSTEM_PROMPT = """\
You are a CV optimization expert. Your job is to select the best content from a master CV \
to match a specific job posting.

You will receive:
1. A job posting (text)
2. Available skills pool (grouped)
3. Available bullet variants per company
4. Available summary templates
5. Available achievement variants

Your task: select the optimal combination that maximizes ATS keyword coverage \
while keeping the CV authentic (never copy phrases from the job posting verbatim).

RULES:
- Select 12-18 skills from the pool, distributed across all 4 groups
- For each company, select the best bullet variant (v1/v2/v3) and how many to include
- Write a new summary (3-5 sentences) with metrics. End with career development direction
- Select the best achievement variant (A, B, or C based on industry fit)
- Active voice always. Metrics over adjectives
- Never copy job posting phrases. Rephrase with concrete evidence

Respond ONLY with valid JSON matching the schema below.
"""

SELECTION_SCHEMA = {
    "type": "object",
    "properties": {
        "analysis": {
            "type": "object",
            "properties": {
                "company_name": {"type": "string"},
                "position_title": {"type": "string"},
                "language": {"type": "string", "enum": ["PL", "EN"]},
                "industry": {"type": "string"},
                "ats_keywords": {"type": "array", "items": {"type": "string"}},
            },
            "required": ["company_name", "position_title", "language", "industry", "ats_keywords"],
        },
        "selected_skills": {
            "type": "array",
            "items": {"type": "string"},
            "description": "List of skill IDs to include (12-18 items)",
        },
        "selected_bullets": {
            "type": "object",
            "description": "Map of company name to list of {bullet_num, version} selections",
            "additionalProperties": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "num": {"type": "string"},
                        "version": {"type": "string"},
                    },
                },
            },
        },
        "selected_achievement": {
            "type": "string",
            "description": "Achievement variant ID (e.g. A_bom, B_ml, C_leadtime)",
        },
        "summary_text": {
            "type": "string",
            "description": "New summary text (LaTeX formatted, 3-5 sentences)",
        },
        "header_title": {
            "type": "string",
            "description": "Position title for the CV header",
        },
        "rodo_company": {
            "type": "string",
            "description": "Company name for the RODO clause",
        },
        "ats_coverage": {
            "type": "object",
            "description": "Map of ATS keyword to where it appears in the CV",
            "additionalProperties": {"type": "string"},
        },
    },
    "required": [
        "analysis", "selected_skills", "selected_bullets",
        "selected_achievement", "summary_text", "header_title",
        "rodo_company", "ats_coverage",
    ],
}


def build_selection_prompt(posting: str, parsed: dict) -> str:
    """Build the prompt for AI content selection."""
    skill_groups = get_skill_groups(parsed)
    companies = set()
    for tag_id in parsed["bullets"]:
        m = re.match(r"^([a-zA-Z]+)_", tag_id)
        if m:
            companies.add(m.group(1))

    parts = [
        "## JOB POSTING\n",
        posting,
        "\n\n## AVAILABLE SKILLS\n",
    ]

    for group_name, skills in skill_groups.items():
        parts.append(f"\n### {group_name}\n")
        for skill_id, content in skills:
            parts.append(f"  {skill_id}: {content}")

    parts.append("\n\n## AVAILABLE BULLETS (per company)\n")
    for company in sorted(companies):
        variants = get_bullet_variants(parsed, company)
        if variants:
            parts.append(f"\n### {company}\n")
            for num, versions in sorted(variants.items()):
                for ver, content in sorted(versions.items()):
                    parts.append(f"  {company}_{num}_{ver}: {content}")

    parts.append("\n\n## AVAILABLE ACHIEVEMENTS (#3 variants)\n")
    for ach_id, content in parsed["achievements"].items():
        parts.append(f"  {ach_id}: {content}")

    parts.append("\n\n## AVAILABLE SUMMARY TEMPLATES\n")
    for sum_id, content in parsed["summaries"].items():
        parts.append(f"  {sum_id}: {content}")

    return "\n".join(parts)


def select_content(posting: str, parsed: dict, api_key: str | None = None) -> dict:
    """Use Claude to select optimal CV content for the job posting.

    Args:
        posting: Job posting text.
        parsed: Output of parse_master().
        api_key: Anthropic API key (or set ANTHROPIC_API_KEY env var).

    Returns:
        Selection dict matching SELECTION_SCHEMA.
    """
    client = Anthropic(api_key=api_key)
    prompt = build_selection_prompt(posting, parsed)

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": prompt}],
    )

    text = response.content[0].text
    json_match = re.search(r"```(?:json)?\s*\n(.*?)\n```", text, re.DOTALL)
    if json_match:
        text = json_match.group(1)

    return json.loads(text)


def _replace_skills_section(tex: str, parsed: dict, selection: dict) -> str:
    """Replace the skills section with selected skills."""
    selected_ids = set(selection.get("selected_skills", []))
    if not selected_ids:
        return tex

    skill_groups = get_skill_groups(parsed)

    group_labels = {
        "planning": "Planowanie i produkcja",
        "analytics": "Analityka i narzędzia",
        "soft_skills": "Kompetencje miękkie",
        "languages": "Języki",
    }

    items_block = []
    for group_key, label in group_labels.items():
        group_skills = skill_groups.get(group_key, [])
        selected_in_group = [(sid, content) for sid, content in group_skills if sid in selected_ids]
        if not selected_in_group:
            continue

        items_block.append(f"{{\\small\\textcolor{{darkgray}}{{{label}}}}}")
        items_block.append(
            "\\begin{itemize}[leftmargin=*,labelsep=0.3cm,"
            "itemsep=-0.05cm,topsep=0cm,label=$\\checkmark$]"
        )
        for _, content in selected_in_group:
            items_block.append(f"    {content}")
        items_block.append("\\end{itemize}")
        items_block.append("\\vspace{0.05cm}")

    new_skills = "\n".join(items_block)

    # Replace: from \sectiontitle{Umiejętności} to the line before %%FROZEN_SECTION:start:loga_firm
    pattern = (
        r"(\\sectiontitle\{Umiejętności\})\n"
        r"(.*?)"
        r"(\n\\vspace\{0\.2cm\}\n\n% %%FROZEN_SECTION:start:loga_firm)"
    )
    def skills_replacer(m):
        return f"{m.group(1)}\n\n{new_skills}\n{m.group(3)}"

    tex = re.sub(pattern, skills_replacer, tex, flags=re.DOTALL)

    return tex


def _replace_summary(tex: str, selection: dict) -> str:
    """Replace the summary text."""
    summary = selection.get("summary_text", "")
    if not summary:
        return tex

    pattern = (
        r"(\\sectiontitle\{Podsumowanie zawodowe\}\\\\)\n"
        r"(.*?)"
        r"(\n\n\\vspace\{0\.1cm\})"
    )

    def summary_replacer(m):
        return f"{m.group(1)}\n{summary}{m.group(3)}"

    tex = re.sub(pattern, summary_replacer, tex, flags=re.DOTALL)

    return tex


def _replace_achievement3(tex: str, parsed: dict, selection: dict) -> str:
    """Replace the 3rd achievement with the selected variant."""
    ach_id = selection.get("selected_achievement", "")
    if not ach_id or ach_id not in parsed["achievements"]:
        return tex

    new_ach = parsed["achievements"][ach_id]

    # The 3rd achievement is the last \item in the achievements block,
    # right after %%FROZEN_SECTION:end:osiagniecie_2
    pattern = (
        r"(% %%FROZEN_SECTION:end:osiagniecie_2\n)"
        r"(    \\item.*?\n)"
        r"(\\end\{itemize\})"
    )

    def ach_replacer(m):
        return f"{m.group(1)}    {new_ach}\n{m.group(3)}"

    tex = re.sub(pattern, ach_replacer, tex, flags=re.DOTALL)

    return tex


def _replace_company_bullets(tex: str, parsed: dict, selection: dict) -> str:
    """Replace bullet items for each company with selected variants."""
    selected_bullets = selection.get("selected_bullets", {})

    for company, bullet_selections in selected_bullets.items():
        if not bullet_selections:
            continue

        # Build new items list
        new_items = []
        for bs in bullet_selections:
            num = bs.get("num", "")
            ver = bs.get("version", "v1")

            # Try exact tag id
            tag_id = f"{company}_{num}_{ver}"
            content = parsed["bullets"].get(tag_id)

            # Fallback: try without version (for bullets with no version suffix)
            if content is None:
                tag_id_noversion = f"{company}_{num}"
                content = parsed["bullets"].get(tag_id_noversion)

            if content:
                new_items.append(f"    {content}")

        if not new_items:
            continue

        items_text = "\n".join(new_items)

        # Find the company's \begin{itemize}...\end{itemize} block
        # Pattern: after \job{CompanyName...} followed by optional newline and \begin{itemize}
        # We match the itemize block that follows each company job entry
        # Use a pattern that matches \begin{itemize}[...]\n...\n\end{itemize}
        # for the specific company section

        # Generic approach: find company name in \job{} and replace next itemize block
        # This works because each company has exactly one itemize block after its \job{}

        # Build company-specific patterns
        company_patterns = {
            "freezco": r"Freezco",
            "maczfit": r"Maczfit",
            "jelenia": r"JeleniaPlast",
            "lginnotek": r"LG Innotek",
            "lgelectronics": r"LG Electronics",
            "acme": r"Acme",
            "startup": r"Startup",
        }

        company_re = company_patterns.get(company, re.escape(company))

        pattern = (
            rf"(\\job\{{{company_re}[^}}]*\}}{{[^}}]*\}}{{[^}}]*\}})\n"
            r"(?:\\begin\{itemize\}[^\n]*\n)?"  # optional begin if directly after
            r"(\\begin\{itemize\}[^\n]*\n)"
            r"(.*?)"
            r"(\\end\{itemize\})"
        )

        # Simpler approach: find itemize block after company job entry
        # Split on \job lines and work section by section
        job_marker = rf"\\job{{{company_re}"
        parts = re.split(f"({re.escape(job_marker[:-len(company_re)])}[^\n]+\n)", tex)

        # Actually, let's use a more robust regex
        # Find: \job{CompanyName...}{...}{...}\n[\n]*\begin{itemize}[...]\n...\end{itemize}
        itemize_pattern = (
            rf"(\\job\{{(?:{company_re})[^}}]*\}}{{[^}}]*\}}{{[^}}]*\}}\n)"
            r"(\n?\\begin\{itemize\}[^\n]*\n)"
            r"(.*?)"
            r"(\\end\{itemize\})"
        )
        match = re.search(itemize_pattern, tex, flags=re.DOTALL)
        if match:
            new_block = (
                f"{match.group(1)}{match.group(2)}"
                f"{items_text}\n{match.group(4)}"
            )
            tex = tex[: match.start()] + new_block + tex[match.end() :]

    return tex


def _replace_header_and_rodo(tex: str, selection: dict) -> str:
    """Replace header title and RODO company name."""
    title = selection.get("header_title", "")
    company = selection.get("rodo_company", "")

    if title:
        # Replace in \fancyhead[R]
        tex = re.sub(
            r"(\\fancyhead\[R\]\{\\textcolor\{darkgray\}\{)[^}]+(\})",
            lambda m: f"{m.group(1)}{title}{m.group(2)}",
            tex,
        )
        # Replace in \fancypagestyle{firstpage}
        tex = re.sub(
            r"(\\fancypagestyle\{firstpage\}\{.*?\\fancyhead\[R\]\{\\textcolor\{darkgray\}\{)[^}]+(\})",
            lambda m: f"{m.group(1)}{title}{m.group(2)}",
            tex,
        )

    if company:
        rodo_text = (
            f"Wyrażam zgodę na przetwarzanie moich danych osobowych przez "
            f"\\textbf{{{company}}} w celu prowadzenia rekrutacji na aplikowane "
            f"przeze mnie stanowisko."
        )
        tex = re.sub(
            r"(\\fancyfoot\[C\]\{\\textcolor\{gray\}\{\\scriptsize\\textit\{).*?(\}\}\})",
            lambda m: f"{m.group(1)}{rodo_text}{m.group(2)}",
            tex,
        )

    return tex


def _strip_comment_tags(tex: str) -> str:
    """Remove all %%TAG comment lines from the final output."""
    lines = tex.split("\n")
    cleaned = []
    for line in lines:
        stripped = line.strip()
        if stripped.startswith("% %%") or stripped.startswith("%% "):
            continue
        # Keep frozen section markers as they're useful for debugging
        # but remove skill/bullet/summary/achievement comment tags
        if re.match(r"^%\s*%%(?:SKILL|BULLET|ACHIEVEMENT|SUMMARY|RODO_COMPANY|HEADER_TITLE):", stripped):
            continue
        # Remove comment-only lines that are just spacing/organization
        if stripped.startswith("% ---") and ("Bullet" in stripped or "GRUPA:" in stripped):
            continue
        cleaned.append(line)
    return "\n".join(cleaned)


def assemble_tex(parsed: dict, selection: dict) -> str:
    """Assemble the final .tex file from master CV and AI selection.

    Takes the raw master .tex and substitutes:
    1. Header title (position name)
    2. RODO company name
    3. Skills section (sidebar)
    4. Summary text
    5. Achievement #3
    6. Company bullet variants

    Frozen sections are preserved verbatim.
    Comment tags are stripped from the output.
    """
    tex = parsed["raw"]

    tex = _replace_header_and_rodo(tex, selection)
    tex = _replace_skills_section(tex, parsed, selection)
    tex = _replace_summary(tex, selection)
    tex = _replace_achievement3(tex, parsed, selection)
    tex = _replace_company_bullets(tex, parsed, selection)
    tex = _strip_comment_tags(tex)

    return tex


def compile_tex(tex_path: str | Path, output_dir: str | Path | None = None) -> Path:
    """Compile .tex to PDF with pdflatex (2 passes).

    Returns path to the generated PDF.
    """
    tex_path = Path(tex_path)
    out_dir = Path(output_dir) if output_dir else tex_path.parent

    for _ in range(2):
        subprocess.run(
            [
                "pdflatex",
                "-interaction=nonstopmode",
                f"-output-directory={out_dir}",
                str(tex_path),
            ],
            capture_output=True,
            check=False,
        )

    return out_dir / tex_path.with_suffix(".pdf").name


def generate_cv(
    posting_path: str | Path,
    master_path: str | Path,
    output_path: str | Path | None = None,
    api_key: str | None = None,
    compile_pdf: bool = True,
) -> dict:
    """Full pipeline: parse master, analyze posting, select content, assemble, compile.

    Args:
        posting_path: Path to job posting file.
        master_path: Path to master CV .tex file.
        output_path: Output .tex path (default: cv_<company>.tex in current dir).
        api_key: Anthropic API key.
        compile_pdf: Whether to compile to PDF after assembly.

    Returns:
        Dict with paths and selection details.
    """
    posting = Path(posting_path).read_text(encoding="utf-8")
    parsed = parse_master(master_path)

    selection = select_content(posting, parsed, api_key=api_key)

    tex_content = assemble_tex(parsed, selection)

    # Determine output path
    if output_path is None:
        company = selection.get("analysis", {}).get("company_name", "output")
        company = re.sub(r"[^a-zA-Z0-9]", "_", company.lower()).strip("_")
        output_path = Path(f"cv_{company}.tex")
    else:
        output_path = Path(output_path)

    output_path.write_text(tex_content, encoding="utf-8")

    result = {
        "tex_path": str(output_path),
        "selection": selection,
    }

    if compile_pdf:
        pdf_path = compile_tex(output_path)
        result["pdf_path"] = str(pdf_path)

    return result
