import shutil
import subprocess
import tempfile
from pathlib import Path

from jinja2 import Environment, FileSystemLoader

from app.models import Recipe
from app.pdf.escape import escape_latex, format_multiline

ASSETS_DIR = Path(__file__).parent / "assets"
TEMPLATES_DIR = Path(__file__).parent / "templates"

_env = Environment(
    loader=FileSystemLoader(TEMPLATES_DIR),
    block_start_string="((*",
    block_end_string="*))",
    variable_start_string="(((",
    variable_end_string=")))",
    comment_start_string="((=",
    comment_end_string="=))",
    trim_blocks=True,
    lstrip_blocks=True,
)

_TIMING_MACROS = [
    ("preptime", "preptime"),
    ("cooktime", "cooktime"),
    ("baketime", "baketime"),
    ("marinade_time", "marinade"),
    ("cooltime", "cooltime"),
    ("robot_time", "robot"),
    ("servings", "servings"),
]


class PdfRenderError(RuntimeError):
    def __init__(self, message: str, log_tail: str):
        super().__init__(message)
        self.log_tail = log_tail


def _build_timing_line(recipe: Recipe) -> str:
    parts = []
    for field, macro in _TIMING_MACROS:
        value = getattr(recipe, field)
        if value:
            parts.append(f"\\{macro}{{{escape_latex(value)}}}")
    return " ".join(parts)


def _build_ingredient_lines(recipe: Recipe) -> list[str]:
    lines = []
    for ing in recipe.ingredients:
        if ing.kind == "section_header":
            lines.append(f"\\textbf{{{escape_latex(ing.text)}}}\\\\")
            continue
        quantity = escape_latex(ing.quantity) if ing.quantity else ""
        unit = escape_latex(ing.unit) if ing.unit else ""
        text = escape_latex(ing.text)
        lines.append(f"\\unit[{quantity}]{{{unit}}} & {text}\\\\")
    return lines


def _build_info_line(recipe: Recipe) -> str | None:
    parts = []
    if recipe.kcal:
        parts.append(f"\\kcal{{{escape_latex(recipe.kcal)}}}")
    if recipe.kh:
        parts.append(f"\\KH{{{escape_latex(recipe.kh)}}}")
    if recipe.protein:
        parts.append(f"\\protein{{{escape_latex(recipe.protein)}}}")
    if recipe.fett:
        parts.append(f"\\fett{{{escape_latex(recipe.fett)}}}")
    return " ".join(parts) if parts else None


def render_recipe_tex(recipe: Recipe) -> str:
    template = _env.get_template("recipe.tex.jinja")
    context = {
        "name": escape_latex(recipe.name),
        "image_filename": recipe.image_filename,
        "timing_line": _build_timing_line(recipe),
        "ingredient_lines": _build_ingredient_lines(recipe),
        "notes": format_multiline(recipe.notes) if recipe.notes else "",
        "step_items": [format_multiline(step.text) for step in recipe.steps],
        "info_line": _build_info_line(recipe),
    }
    return template.render(**context)


def compile_recipe_pdf(recipe: Recipe, upload_dir: Path) -> bytes:
    tex_source = render_recipe_tex(recipe)

    with tempfile.TemporaryDirectory(prefix="recipe-pdf-") as tmp:
        workdir = Path(tmp)
        (workdir / "Pictures" / "ico").mkdir(parents=True)
        shutil.copy(ASSETS_DIR / "cuisine.sty", workdir / "cuisine.sty")
        for icon in (ASSETS_DIR / "icons").glob("*.png"):
            shutil.copy(icon, workdir / "Pictures" / "ico" / icon.name)
        if recipe.image_filename:
            src = upload_dir / recipe.image_filename
            if src.exists():
                shutil.copy(src, workdir / "Pictures" / recipe.image_filename)

        tex_path = workdir / "recipe.tex"
        tex_path.write_text(tex_source, encoding="utf-8")

        for _ in range(2):
            subprocess.run(
                ["xelatex", "-interaction=nonstopmode", "-halt-on-error", tex_path.name],
                cwd=workdir,
                capture_output=True,
                text=True,
                timeout=60,
            )

        pdf_path = workdir / "recipe.pdf"
        if not pdf_path.exists():
            log_path = workdir / "recipe.log"
            log_tail = ""
            if log_path.exists():
                log_tail = "\n".join(log_path.read_text(errors="ignore").splitlines()[-40:])
            raise PdfRenderError("PDF compilation failed", log_tail)

        return pdf_path.read_bytes()
