import base64
import html
import os
import re
from functools import lru_cache
from pathlib import Path
from urllib.parse import quote


def default_content_root() -> Path:
    current = Path(__file__).resolve()
    candidates = [
        current.parents[1] / "contenus_pedagogiques",
        current.parents[1] / "Programme" / "contenus_pedagogiques",
    ]
    candidates.extend(parent / "Programme" / "contenus_pedagogiques" for parent in current.parents)
    candidates.extend(
        [
            Path("/programme/contenus_pedagogiques"),
            Path("/app/contenus_pedagogiques"),
            Path("/app/Programme/contenus_pedagogiques"),
        ]
    )
    return next((candidate for candidate in candidates if candidate.exists()), candidates[0])


CONTENT_ROOT = Path(os.getenv("PROGRAM_CONTENT_ROOT", default_content_root())).resolve()
LESSON_BACKGROUND = "#f8fafc"

GRADE_ALIASES = {
    "CM1": "cm1",
    "CM2": "cm2",
    "6e": "6e",
    "6eme": "6e",
    "6ieme": "6e",
    "5e": "5e",
    "5eme": "5e",
    "5ieme": "5e",
    "4e": "4e",
    "4eme": "4e",
    "4ieme": "4e",
    "3e": "3e",
    "3eme": "3e",
    "3ieme": "3e",
}


def normalize_grade(grade: str) -> str:
    compact = (grade or "").strip().replace("è", "e").replace("é", "e")
    return GRADE_ALIASES.get(compact, compact.lower())


def title_from_slug(slug: str) -> str:
    parts = slug.split("_")
    if parts and parts[0][:2].isdigit():
        parts = parts[1:]
    return " ".join(part.capitalize() for part in parts if part)


def encode_asset_path(relative_path: str) -> str:
    payload = relative_path.encode("utf-8")
    return base64.urlsafe_b64encode(payload).decode("ascii").rstrip("=")


def decode_asset_token(token: str) -> Path:
    padding = "=" * (-len(token) % 4)
    relative = base64.urlsafe_b64decode(f"{token}{padding}").decode("utf-8")
    path = (CONTENT_ROOT / relative).resolve()
    if CONTENT_ROOT not in path.parents:
        raise ValueError("Chemin de ressource invalide")
    return path


def build_asset_url(relative_path: str) -> str:
    return f"/api/program/assets/{quote(encode_asset_path(relative_path))}"


def build_section_url(relative_path: str, section_index: int) -> str:
    token = quote(encode_asset_path(relative_path))
    return f"/api/program/assets/{token}/sections/{section_index}"


def find_section_context_path(svg_path: Path, asset_number: int, section_number: int) -> Path | None:
    context_dir = svg_path.parent.parent / "card_contexts" / svg_path.stem
    candidates = [
        context_dir / f"Fiche{asset_number}Card{section_number}.md",
        context_dir / f"fiche_{asset_number:02d}_card_{section_number:02d}.md",
        context_dir / f"card_{section_number:02d}.md",
    ]
    return next((candidate for candidate in candidates if candidate.exists()), None)


def extract_svg_sections(svg_path: Path, asset_number: int | None = None) -> list[dict]:
    text = svg_path.read_text(encoding="utf-8", errors="ignore")
    sections: list[dict] = []
    pattern = re.compile(
        r'<g\s+transform="translate\((?P<x>[-\d.]+)\s+(?P<y>[-\d.]+)\)">\s*'
        r'<rect\s+x="0"\s+y="0"\s+width="(?P<w>[-\d.]+)"\s+height="(?P<h>[-\d.]+)"[^>]*>'
        r'(?P<body>.*?)</g>',
        re.DOTALL,
    )
    for match in pattern.finditer(text):
        width = float(match.group("w"))
        height = float(match.group("h"))
        if width < 250 or height < 120:
            continue
        title_match = re.search(r'<text[^>]*>(?P<title>.*?)</text>', match.group("body"), re.DOTALL)
        raw_title = re.sub(r"<[^>]+>", "", title_match.group("title") if title_match else "")
        title = html.unescape(raw_title).strip() or f"Étape {len(sections) + 1}"
        margin = 28
        section_number = len(sections) + 1
        context_path = find_section_context_path(svg_path, asset_number or 1, section_number)
        sections.append(
            {
                "index": len(sections),
                "title": title,
                "context_key": f"Fiche{asset_number or 1}Card{section_number}",
                "context": context_path.read_text(encoding="utf-8", errors="ignore").strip()
                if context_path
                else "",
                "view_box": [
                    max(float(match.group("x")) - margin, 0),
                    max(float(match.group("y")) - margin, 0),
                    width + margin * 2,
                    height + margin * 2,
                ],
            }
        )
    return sections


def render_svg_section(path: Path, section_index: int) -> str:
    sections = extract_svg_sections(path)
    if section_index < 0 or section_index >= len(sections):
        raise IndexError("Section introuvable")
    x, y, width, height = sections[section_index]["view_box"]
    text = path.read_text(encoding="utf-8", errors="ignore")
    first_group_index = text.find("<g ")
    if first_group_index > -1:
        before_groups = text[:first_group_index]
        after_groups = text[first_group_index:]
        before_groups = re.sub(
            r"\.bg\s*\{[^}]*\}",
            f".bg {{ fill: {LESSON_BACKGROUND}; }}",
            before_groups,
            count=1,
        )
        before_groups = re.sub(
            r"\.panel\s*\{[^}]*\}",
            (
                ".panel { fill: #ffffff; stroke: #d5e0dc; stroke-width: 3; "
                "filter: drop-shadow(0 10px 24px rgba(22, 42, 45, 0.08)); }"
            ),
            before_groups,
            count=1,
        )
        before_groups = re.sub(
            r'(<rect\b(?=[^>]*class="panel")(?=[^>]*/>)[^>]*)/>',
            rf'\1 style="fill:{LESSON_BACKGROUND};stroke:none;filter:none"/>',
            before_groups,
            count=1,
        )
        text = before_groups + after_groups
    text = re.sub(r'\swidth="[^"]+"', f' width="{int(width)}"', text, count=1)
    text = re.sub(r'\sheight="[^"]+"', f' height="{int(height)}"', text, count=1)
    text = re.sub(
        r'\sviewBox="[^"]+"',
        f' viewBox="{x:g} {y:g} {width:g} {height:g}"',
        text,
        count=1,
    )
    return text


@lru_cache(maxsize=1)
def list_lessons() -> list[dict]:
    lessons: list[dict] = []
    if not CONTENT_ROOT.exists():
        return lessons

    for svg_dir in sorted(CONTENT_ROOT.glob("cycle_*/*/*/**/svg")):
        if not svg_dir.is_dir():
            continue

        svg_files = sorted(svg_dir.glob("*.svg"))
        if not svg_files:
            continue

        lesson_dir = svg_dir.parent
        relative_lesson = lesson_dir.relative_to(CONTENT_ROOT).as_posix()
        parts = relative_lesson.split("/")
        if len(parts) < 4:
            continue

        cycle, subject, grade = parts[0], parts[1], parts[2]
        title = title_from_slug(lesson_dir.name)
        readme = lesson_dir / "README.md"
        if readme.exists():
            first_heading = next(
                (
                    line.lstrip("#").strip()
                    for line in readme.read_text(encoding="utf-8", errors="ignore").splitlines()
                    if line.startswith("#")
                ),
                "",
            )
            title = first_heading or title

        assets = []
        for asset_index, svg_path in enumerate(svg_files, start=1):
            relative_path = svg_path.relative_to(CONTENT_ROOT).as_posix()
            sections = extract_svg_sections(svg_path, asset_index)
            assets.append(
                {
                    "name": svg_path.stem,
                    "title": title_from_slug(svg_path.stem),
                    "path": relative_path,
                    "url": build_asset_url(relative_path),
                    "sections": [
                        {
                            **section,
                            "url": build_section_url(relative_path, section["index"]),
                        }
                        for section in sections
                    ],
                }
            )

        lessons.append(
            {
                "id": relative_lesson,
                "title": title,
                "subject": title_from_slug(subject),
                "grade": grade,
                "cycle": cycle,
                "asset_count": len(assets),
                "assets": assets,
            }
        )

    return lessons


def list_lessons_for_grade(grade: str) -> list[dict]:
    normalized = normalize_grade(grade)
    return [lesson for lesson in list_lessons() if lesson["grade"] == normalized]


def get_content_status() -> dict:
    lessons = list_lessons()
    by_grade: dict[str, int] = {}
    for lesson in lessons:
        by_grade[lesson["grade"]] = by_grade.get(lesson["grade"], 0) + 1
    return {
        "content_root": str(CONTENT_ROOT),
        "content_root_exists": CONTENT_ROOT.exists(),
        "lesson_count": len(lessons),
        "lessons_by_grade": by_grade,
    }


def get_lesson(lesson_id: str) -> dict | None:
    return next((lesson for lesson in list_lessons() if lesson["id"] == lesson_id), None)
