#!/usr/bin/env python3
"""
Shared Curriculum Rendering Engine

Renders JSON outlines into PDFs via Jinja2 templates + WeasyPrint.
Subject-agnostic — each subject provides its own Jinja2 template.

Usage:
    from render import render_outline, render_week, render_all
    render_outline(outline_path, template_dir, output_dir)
"""

import json
import os
import re
import sys
import glob
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, TemplateNotFound
from weasyprint import HTML


# ─── Jinja2 Setup ──────────────────────────────────────────────────────────

def create_env(template_dir: str) -> Environment:
    """Create a Jinja2 environment with the given template directory."""
    env = Environment(
        loader=FileSystemLoader(template_dir),
        autoescape=False,  # We control the content
        keep_trailing_newline=True,
    )
    # Helpful filters
    env.filters["enumerate"] = enumerate
    env.filters["split"] = lambda s, sep=",": s.split(sep)
    # Convert underscore blanks (______) to proper answer spans
    env.filters["blanks"] = lambda s: re.sub(r'_+', '<span class="fill-blank-line"></span>', s)
    # Safe dict access for keys that conflict with dict methods (items, keys, values)
    env.globals["val"] = lambda d, k, default="": d[k] if isinstance(d, dict) and k in d else (getattr(d, k, default) if hasattr(d, k) else default)
    env.filters["capitalize"] = str.capitalize
    env.filters["upper"] = str.upper
    env.filters["lower"] = str.lower
    env.filters["length"] = len
    env.filters["first"] = lambda l: l[0] if l else ""
    env.filters["last"] = lambda l: l[-1] if l else ""
    env.filters["chunk"] = lambda l, n: [l[i:i + n] for i in range(0, len(l), n)]
    return env


# ─── Core Rendering ────────────────────────────────────────────────────────

def render_outline(
    outline_path: str,
    template_dir: str,
    output_dir: str,
    template_name: str = None,
    verbose: bool = False,
) -> str:
    """
    Render a single outline JSON into an HTML file, then PDF.

    Args:
        outline_path: Path to outline JSON file
        template_dir: Directory containing Jinja2 template(s)
        output_dir: Directory to write HTML and PDF to
        template_name: Template filename (e.g. 'lesson.html'). If None, picks the first .html template.
        verbose: Print progress info

    Returns:
        Path to generated PDF
    """
    outline_path = Path(outline_path)
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    # Load outline
    with open(outline_path) as f:
        outline = json.load(f)

    # Resolve template
    env = create_env(template_dir)
    if template_name is None:
        templates = list(Path(template_dir).glob("*.html"))
        if not templates:
            raise FileNotFoundError(f"No .html templates found in {template_dir}")
        template_name = templates[0].name

    try:
        template = env.get_template(template_name)
    except TemplateNotFound:
        raise FileNotFoundError(f"Template '{template_name}' not found in {template_dir}")

    # Render HTML
    html_content = template.render(**outline)

    # Write HTML
    stem = outline_path.stem
    html_path = output_dir / f"{stem}.html"
    with open(html_path, "w") as f:
        f.write(html_content)

    if verbose:
        print(f"  HTML → {html_path} ({len(html_content)} chars)")

    # Render PDF
    pdf_path = output_dir / f"{stem}.pdf"
    HTML(string=html_content).write_pdf(str(pdf_path))

    if verbose:
        print(f"  PDF  → {pdf_path}")

    return str(pdf_path)


def render_week(
    outlines_dir: str,
    template_dir: str,
    output_base: str,
    week_num: int,
    template_name: str = None,
    days: list = None,
    verbose: bool = False,
) -> list:
    """
    Render all days in a week.

    Args:
        outlines_dir: Directory containing outline JSON files
        template_dir: Directory containing Jinja2 template(s)
        output_base: Base output directory (week subdirs created here)
        week_num: Week number (e.g. 1)
        template_name: Template filename
        days: List of day names to render (e.g. ['Monday', 'Wednesday', 'Friday'])
        verbose: Print progress info

    Returns:
        List of generated PDF paths
    """
    outlines_dir = Path(outlines_dir)
    output_base = Path(output_base)
    week_dir = output_base / f"Week{week_num:02d}"
    week_dir.mkdir(parents=True, exist_ok=True)

    if days is None:
        days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

    pdfs = []
    for day in days:
        pattern = f"Week{week_num:02d}_{day}*.json"
        matches = sorted(outlines_dir.glob(pattern))
        if not matches:
            if verbose:
                print(f"  SKIP: No outline for Week {week_num} {day}")
            continue

        for outline_path in matches:
            pdf = render_outline(
                outline_path,
                template_dir,
                week_dir,
                template_name=template_name,
                verbose=verbose,
            )
            pdfs.append(pdf)

    return pdfs


def render_all(
    outlines_dir: str,
    template_dir: str,
    output_base: str,
    total_weeks: int = 32,
    template_name: str = None,
    days: list = None,
    verbose: bool = False,
) -> list:
    """
    Render all weeks in a course.

    Returns:
        List of all generated PDF paths
    """
    all_pdfs = []
    for week in range(1, total_weeks + 1):
        pdfs = render_week(
            outlines_dir,
            template_dir,
            output_base,
            week,
            template_name=template_name,
            days=days,
            verbose=verbose,
        )
        all_pdfs.extend(pdfs)
        if verbose:
            print(f"  Week {week}: {len(pdfs)} PDF(s)")
    return all_pdfs


# ─── Validation ────────────────────────────────────────────────────────────

def validate_outline(outline_path: str) -> dict:
    """
    Validate an outline JSON has required fields.

    Returns dict with 'valid' bool and 'errors' list.
    """
    with open(outline_path) as f:
        outline = json.load(f)

    errors = []

    # Required fields
    required = ["week", "day", "topic"]
    for field in required:
        if field not in outline:
            errors.append(f"Missing required field: {field}")

    # Check pages
    if "pages" in outline:
        if not isinstance(outline["pages"], list) or len(outline["pages"]) == 0:
            errors.append("'pages' must be a non-empty list")
    elif "reading" not in outline and "activities" not in outline:
        errors.append("Outline must have 'pages', 'reading', or 'activities'")

    return {"valid": len(errors) == 0, "errors": errors}


if __name__ == "__main__":
    # Quick test
    import argparse

    parser = argparse.ArgumentParser(description="Render curriculum outlines to PDF")
    parser.add_argument("outline", help="Path to outline JSON or week directory")
    parser.add_argument("-t", "--template-dir", required=True, help="Template directory")
    parser.add_argument("-o", "--output-dir", required=True, help="Output directory")
    parser.add_argument("--template", help="Specific template file name")
    parser.add_argument("-v", "--verbose", action="store_true")

    args = parser.parse_args()

    outline_path = Path(args.outline)
    if outline_path.is_dir():
        # Render all outlines in directory
        for json_file in sorted(outline_path.glob("*.json")):
            render_outline(
                json_file,
                args.template_dir,
                args.output_dir,
                template_name=args.template,
                verbose=args.verbose,
            )
    else:
        render_outline(
            outline_path,
            args.template_dir,
            args.output_dir,
            template_name=args.template,
            verbose=args.verbose,
        )
