"""Tests for the static alignment preview HTML renderer."""

import json
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "scripts"))


def _fixture_graph():
    import alignment_graph_preview as agp

    return agp.build_graph_from_course_config({
        "course": {"id": "999", "name": "Preview Course"},
        "clos": [
            {"id": "CLO1", "label": "Interpretation", "text": "Interpret fitted statistical models."},
            {"id": "CLO2", "label": "Communication", "text": "Explain statistical findings to audiences."},
        ],
        "modules": [
            {
                "number": 1,
                "title": "Module 1",
                "mlos_draft": [
                    {
                        "id": "MLO1.1",
                        "text": "Interpret a fitted regression coefficient in context.",
                        "ladders_to": "CLO1",
                    }
                ],
                "learning_materials": [
                    {
                        "title": "Regression reading",
                        "type": "reading",
                        "text": "Textbook section on regression coefficients.",
                        "mlo_ids": ["MLO1.1"],
                    }
                ],
                "assessments": [
                    {
                        "type": "quiz",
                        "title": "M1 Quiz",
                        "mlo_ids": ["MLO1.1"],
                        "questions": [
                            {
                                "id": "Q1",
                                "text": "Which interpretation of the slope is best?",
                                "mlo_ids": ["MLO1.1"],
                            },
                            {
                                "id": "Q2",
                                "text": "Which answer is numerically largest?",
                            },
                        ],
                    },
                    {
                        "type": "assignment",
                        "title": "M1 Homework",
                        "mlo_ids": ["MLO1.1"],
                        "rubric": [
                            {
                                "id": "criterion-interpretation",
                                "text": "Interprets the coefficient with units and context.",
                                "mlo_ids": ["MLO1.1"],
                            }
                        ],
                    },
                ],
            },
            {
                "number": 2,
                "title": "Module 2",
                "mlos_draft": [
                    {
                        "id": "MLO2.1",
                        "text": "Explain statistical findings in plain language.",
                        "ladders_to": "CLO2",
                    }
                ],
            },
        ],
    })


def test_render_alignment_preview_includes_required_sections_and_faculty_safe_labels():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())

    assert "<h2>Course Summary</h2>" in html
    assert "<h2>Alignment Paths</h2>" in html
    assert "<h2>Possible Gaps</h2>" in html
    assert "<h2>Assessment Detail</h2>" in html
    assert "<h2>Faculty Review Notes</h2>" in html
    assert "Explicit evidence" in html
    assert "Likely alignment" in html
    assert "Needs review" in html
    assert "bad alignment" not in html.lower()
    assert "failed assessment" not in html.lower()


def test_render_alignment_preview_exposes_filters_and_gap_evidence():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())

    assert 'id="filter-module"' in html
    assert 'id="filter-clo"' in html
    assert 'id="filter-mlo"' in html
    assert 'id="filter-type"' in html
    assert 'id="filter-confidence"' in html
    assert "split(/\\s+/).includes(value)" in html
    assert "MLO has no assessment evidence" in html
    assert "Quiz question needs MLO review" in html
    assert "Which answer is numerically largest?" in html


def test_write_preview_creates_static_html_file(tmp_path):
    import alignment_preview_html as renderer

    output_path = tmp_path / "alignment-preview.html"
    renderer.write_preview(_fixture_graph(), output_path)

    html = output_path.read_text(encoding="utf-8")
    assert "<!doctype html>" in html
    assert "Preview Course" in html
    assert "Generated from local-course-config graph data" in html


def test_render_alignment_preview_warns_when_clos_or_mlos_missing():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview({
        "schema_version": "alignment-graph-preview/v1",
        "course_id": "999",
        "course_title": "Empty Course",
        "source_mode": "local-course-config",
        "nodes": [],
        "edges": [],
        "gaps": {},
    })

    assert "Needs setup review" in html
    assert "No CLO nodes were found in this graph." in html
    assert "No MLO nodes were found in this graph." in html


def test_cli_renders_default_output_path(tmp_path, monkeypatch):
    import alignment_preview_html as renderer

    graph = _fixture_graph()
    graph_dir = tmp_path / "courses" / "999" / "alignment"
    graph_dir.mkdir(parents=True)
    (graph_dir / "graph.json").write_text(json.dumps(graph), encoding="utf-8")
    monkeypatch.setattr(renderer, "PLUGIN_ROOT", tmp_path)

    assert renderer.main(["--course-id", "999"]) == 0
    output_path = graph_dir / "alignment-preview.html"
    assert output_path.exists()
    assert "Alignment Preview" in output_path.read_text(encoding="utf-8")


# ---------------------------------------------------------------------------
# Phase 3 — Graphify-style visualization tests
# ---------------------------------------------------------------------------


def test_graph_view_renders_svg_and_vendor_d3_script():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())

    # SVG element with stable id for click-to-filter integration.
    assert '<svg id="alignment-graph"' in html
    assert 'role="img"' in html
    assert 'Alignment Graph</h2>' in html

    # Vendor d3.js v7 marker — the upstream banner from jsdelivr.
    assert "https://d3js.org v7" in html
    # And the d3 namespace bootstrap.
    assert ".d3=t.d3||{}" in html or "self.d3=" in html


def test_graph_view_tables_still_render_for_accessibility_fallback():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    # All 5 original sections must still render below the new graph view.
    for section_h2 in (
        "Course Summary",
        "Alignment Paths",
        "Possible Gaps",
        "Assessment Detail",
        "Faculty Review Notes",
    ):
        assert f"<h2>{section_h2}</h2>" in html or f">{section_h2}<" in html, (
            f"Section '{section_h2}' missing — graph view must be additive, not replacement."
        )


def test_graph_view_carries_node_and_edge_data_attrs_for_js():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    # The graph payload is embedded as <script type="application/json">.
    assert 'id="graph-data"' in html
    assert 'type="application/json"' in html
    # And the inline JS reads `data-node-type` + `data-confidence` attrs.
    assert 'data-node-type' in html
    assert 'data-confidence' in html


def test_graph_view_click_to_filter_handler_present():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    # Inline JS must wire a node click handler that toggles
    # `dimmed-by-graph-filter` on `.filter-row` elements.
    assert "node.on('click'" in html
    assert "dimmed-by-graph-filter" in html
    assert "filter-row" in html


def test_graph_view_hierarchical_layout_toggle_present():
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    assert 'id="layout-toggle"' in html
    assert "Switch to hierarchical layout" in html
    # The toggle logic must distinguish hierarchical from force layout.
    assert "hierarchical = !hierarchical" in html


def test_graph_view_fails_fast_when_vendor_d3_missing(monkeypatch, tmp_path):
    import alignment_preview_html as renderer

    # Point VENDOR_D3_PATH at a non-existent file.
    monkeypatch.setattr(renderer, "VENDOR_D3_PATH", tmp_path / "missing-d3.min.js")
    try:
        renderer.render_alignment_preview(_fixture_graph())
        assert False, "render should fail fast on missing vendor d3.js"
    except FileNotFoundError as exc:
        assert "Vendor d3.js not found" in str(exc)


def test_alignment_paths_cell_inlines_clo_and_mlo_text_snippets():
    """Per user feedback (2026-05-23): tables must show LO text inline so
    reviewers don't need to memorize what CLO1 / MLO1.1 actually are."""
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    # Path cell now carries a small-text snippet block under the arrow path.
    # The CLO and MLO text from the fixture must surface inside that block.
    assert "Interpret fitted statistical models." in html
    assert "Interpret a fitted regression coefficient in context." in html
    # Snippet uses the dedicated muted text style so the path label stays scannable.
    assert "color:var(--muted)" in html


def test_assessment_detail_lists_full_mlo_text_next_to_label():
    """Per user feedback: 'mapped MLO evidence' list must show MLO text,
    not just the label + edge evidence."""
    import alignment_preview_html as renderer

    html = renderer.render_alignment_preview(_fixture_graph())
    # The MLO text appears alongside the MLO label in the assessment card list.
    # Fixture MLO1.1 text -> "Interpret a fitted regression coefficient in context."
    # The label and text must coexist in the same list item.
    import re

    pattern = re.compile(
        r"<li[^>]*>.*?MLO1\.1.*?Interpret a fitted regression coefficient.*?</li>",
        re.S,
    )
    assert pattern.search(html), "MLO text not found alongside MLO1.1 label in assessment card"


def test_graph_payload_excludes_oversized_text():
    import alignment_preview_html as renderer

    big_text = "x" * 5000
    graph = {
        "schema_version": "alignment-graph-preview/v1",
        "course_id": "888",
        "course_title": "Big Text Test",
        "source_mode": "canvas-readonly",
        "nodes": [
            {"id": "clo:c1:aaa", "type": "clo", "label": "CLO1", "text": big_text, "metadata": {}},
        ],
        "edges": [],
        "gaps": {},
    }
    payload = renderer._graph_payload(graph)
    # _truncate caps text to 140 chars + ellipsis — keeps embedded JSON compact.
    assert len(payload["nodes"][0]["text"]) <= 160
