"""Tests for the Phase A /alignment skill orchestrator."""

import json
import os
import sys
from pathlib import Path

import pytest

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


def test_alignment_modules_import():
    import alignment_skill  # noqa: F401
    import alignment_validator  # noqa: F401


def _base_manifest() -> dict:
    return {
        "course_id": "123",
        "course_name": "Test Course",
        "syllabus_html": "<p>Course objective: Analyze systems.</p>",
        "syllabus_url": "https://canvas.test/courses/123/assignments/syllabus",
        "module_overviews_html": {
            "10": {
                "html": "<p>Module objective: Apply system models.</p>",
                "page_url": "https://canvas.test/courses/123/pages/m1-overview",
            }
        },
        "assessments": [
            {
                "id": "assignment-1",
                "title": "Artifact",
                "type": "summative",
                "canvas_type": "Assignment",
                "module": 10,
                "mlo_ids": [],
                "clo_ids": [],
            }
        ],
        "materials": [
            {
                "id": "page-1",
                "title": "Lesson",
                "type": "page",
                "canvas_type": "Page",
                "module": 10,
                "mlo_ids": [],
            }
        ],
        "extracted_clos": [
            {
                "text": "Analyze systems.",
                "page_url": "https://canvas.test/courses/123/assignments/syllabus",
                "confidence": "high",
            }
        ],
        "extracted_mlos_by_module": {
            "10": [
                {
                    "text": "Apply system models.",
                    "page_url": "https://canvas.test/courses/123/pages/m1-overview",
                    "confidence": "high",
                }
            ]
        },
        "declared_clos": [{"id": "CLO-1", "text": "Analyze systems."}],
        "declared_mlos": {"1": [{"id": "M1.1", "text": "Apply system models.", "clo_ids": ["CLO-1"]}]},
    }


def _write_manifest(tmp_path: Path, manifest: dict) -> Path:
    path = tmp_path / "extract_manifest.json"
    path.write_text(json.dumps(manifest), encoding="utf-8")
    return path


def test_happy_path_scrape_manifest_then_report_renders(tmp_path, monkeypatch):
    import alignment_skill
    import canvas_api

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    course_dir = tmp_path / "courses" / "123"
    course_dir.mkdir(parents=True)
    (course_dir / "course-config.json").write_text(
        json.dumps({
            "course": {"title": "Test Course"},
            "clos": [{"id": "CLO-1", "text": "Analyze systems."}],
            "modules": [
                {"number": 1, "objectives": [{"id": "M1.1", "text": "Apply system models.", "clo": "CLO-1"}]}
            ],
        }),
        encoding="utf-8",
    )

    monkeypatch.setattr(canvas_api, "get_config", lambda course_id: {"course_id": course_id})
    monkeypatch.setattr(canvas_api, "get_syllabus", lambda _config: {
        "html": "<p>Analyze systems.</p>",
        "page_url": "https://canvas.test/syllabus",
        "updated_at": None,
    })
    monkeypatch.setattr(alignment_skill, "_fetch_course_tree", lambda _config: [
        {
            "id": 10,
            "name": "Module 1",
            "position": 1,
            "items": [
                {"id": 100, "type": "Page", "title": "Lesson", "page_url": "m1-lesson"},
                {"id": 200, "type": "Assignment", "title": "Artifact", "content_id": 1},
            ],
        }
    ])
    monkeypatch.setattr(canvas_api, "get_module_overview_page", lambda _config, _module_id: {
        "html": "<p>Apply system models.</p>",
        "page_url": "https://canvas.test/pages/m1-overview",
        "page_slug": "m1-overview",
        "module_id": 10,
    })
    monkeypatch.setattr(canvas_api, "list_assignments", lambda _config: [
        {"id": 1, "name": "Artifact", "points_possible": 30}
    ])

    manifest_path = tmp_path / "manifest.json"
    alignment_skill.run_scrape("123", manifest_path, tmp_path / "standards.yaml")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    manifest["extracted_clos"] = _base_manifest()["extracted_clos"]
    manifest["extracted_mlos_by_module"] = _base_manifest()["extracted_mlos_by_module"]
    manifest_path.write_text(json.dumps(manifest), encoding="utf-8")

    report_path = alignment_skill.run_report("123", manifest_path)
    report = report_path.read_text(encoding="utf-8")

    assert "Alignment Report" in report
    assert "Analyze systems." in report
    assert "Artifact" in report


def test_declared_clo_divergence_surfaces_in_report(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = _base_manifest()
    manifest["declared_clos"] = [{"id": "CLO-1", "text": "Evaluate policy tradeoffs."}]
    report_path = alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))

    report = report_path.read_text(encoding="utf-8")
    assert "Declared/extracted divergence" in report
    assert "Evaluate policy tradeoffs." in report


def test_empty_sources_render_extraction_failed_report(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = {
        "course_id": "123",
        "course_name": "Empty Course",
        "syllabus_html": "",
        "syllabus_url": "",
        "module_overviews_html": {},
        "assessments": [],
        "materials": [],
        "extracted_clos": [],
        "extracted_mlos_by_module": {},
    }

    report_path = alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))
    report = report_path.read_text(encoding="utf-8")

    assert "Extraction failed" in report
    assert "No alignment matrix available." in report


def test_low_confidence_extraction_flag_is_visible(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = _base_manifest()
    manifest["extracted_clos"][0]["confidence"] = "low"
    report_path = alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))

    report = report_path.read_text(encoding="utf-8")
    assert "LOW CONFIDENCE" in report
    assert "Low-confidence extraction" in report


@pytest.mark.parametrize(
    "header,expected_confidence",
    [
        ("Course Learning Outcomes", "high"),
        ("Learning Outcomes", "high"),
        ("Course Outcomes", "high"),
        ("Course Objectives", "low"),
        ("Student Learning Outcomes", "high"),
        ("CLO", "high"),
    ],
)
def test_regex_clo_multi_pattern_extracts_lists(header, expected_confidence):
    import alignment_skill

    syllabus = f"""
    <h2>{header}</h2>
    <ul>
      <li>Analyze clinical case evidence.</li>
      <li>Evaluate intervention options.</li>
    </ul>
    <h2>Learning Objectives</h2>
    <ul><li>Do not treat this as a CLO.</li></ul>
    """

    clos, header_used = alignment_skill.regex_extract_clos(syllabus, "https://canvas.test/syllabus")

    assert header_used == header
    assert [item["text"] for item in clos] == [
        "Analyze clinical case evidence.",
        "Evaluate intervention options.",
    ]
    assert {item["confidence"] for item in clos} == {expected_confidence}
    assert {item["extraction_method"] for item in clos} == {"regex"}


@pytest.mark.parametrize(
    "body,expected_classification,expected_count",
    [
        (
            "<h2>Learning Objectives</h2><ul><li>Apply case concepts.</li><li>Analyze data.</li></ul>",
            "mlo_section",
            2,
        ),
        ("<h2>Topics</h2><ul><li>Case background</li><li>Terminology</li></ul>", "topics_only", 0),
        (f"<p>{'Narrative overview content. ' * 12}</p>", "prose_only", 0),
        ("<p>Short intro.</p>", "empty", 0),
    ],
)
def test_regex_mlo_classification_variants(body, expected_classification, expected_count):
    import alignment_skill

    mlos, classification = alignment_skill.regex_extract_mlos(body, 3, "https://canvas.test/pages/m3-overview")

    assert classification == expected_classification
    assert len(mlos) == expected_count
    if mlos:
        assert mlos[0]["module_id"] == 3
        assert mlos[0]["extraction_method"] == "regex"


def test_module_completeness_findings_include_severities_and_recommendations():
    import alignment_skill

    manifest = {
        "modules": [
            {"id": 1, "position": 1, "classification": "mlo_section", "overview_page_url": "u1"},
            {"id": 2, "position": 2, "classification": "topics_only", "overview_page_url": "u2"},
            {"id": 3, "position": 3, "classification": "prose_only", "overview_page_url": "u3"},
            {"id": 4, "position": 4, "classification": "empty", "overview_page_url": "u4"},
        ],
        "module_overviews_html": {},
    }

    findings = alignment_skill._module_completeness_findings(manifest)
    by_module = {item["module"]: item for item in findings}

    assert by_module[1]["severity"] == "INFO"
    assert by_module[1]["recommendation"] == ""
    assert by_module[2]["severity"] == "WARN"
    assert by_module[2]["recommendation"] == "Add <h2>Learning Objectives</h2> with <ul> listing observable outcomes."
    assert by_module[3]["severity"] == "WARN"
    assert by_module[3]["recommendation"] == "Add one or run /course-build to scaffold."
    assert by_module[4]["severity"] == "FAIL"
    assert "insufficient content" in by_module[4]["finding"]


def test_mandatory_handoff_banner_when_no_clos_or_config(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = {
        "course_id": "123",
        "course_name": "No CLO Course",
        "syllabus_html": "",
        "syllabus_url": "",
        "module_overviews_html": {},
        "assessments": [],
        "materials": [],
        "extracted_clos_regex_pass": [],
        "extracted_mlos_by_module_regex_pass": {},
        "extracted_clos": [],
        "extracted_mlos_by_module": {},
    }

    report_path = alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))
    report = report_path.read_text(encoding="utf-8")

    assert "No course-level objectives found in syllabus." in report
    assert "/course-build" in report
    assert "cannot map MLOs → CLOs" in report


def test_remediation_brief_schema_from_report_findings(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = _base_manifest()
    manifest["modules"] = [
        {
            "id": 3,
            "position": 3,
            "classification": "prose_only",
            "overview_page_url": "https://canvas.test/courses/123/pages/m3-overview",
        }
    ]

    alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))
    brief_path = tmp_path / "courses" / "123" / "staging" / "alignment" / "remediation_brief.json"
    brief = json.loads(brief_path.read_text(encoding="utf-8"))

    assert brief["source"] == "alignment"
    assert brief["course_id"] == "123"
    assert "generated_at" in brief
    assert brief["criteria_to_remediate"]
    item = brief["criteria_to_remediate"][0]
    assert set(item) == {"criterion_id", "module", "severity", "finding", "recommendation", "evidence_url"}
    assert item["criterion_id"] == "alignment.M3.no-mlo-section"
    assert item["module"] == 3
    assert item["severity"] == "WARN"
    assert "no explicit Learning Objectives section" in item["finding"]


def test_bloom_progression_regression_warns_in_report(tmp_path, monkeypatch):
    import alignment_skill

    monkeypatch.setattr(alignment_skill, "PLUGIN_ROOT", tmp_path)
    manifest = _base_manifest()
    manifest["extracted_mlos_by_module"] = {
        "1": [{"text": "Create an intervention plan.", "page_url": "u1", "confidence": "high"}],
        "2": [{"text": "Analyze case evidence.", "page_url": "u2", "confidence": "high"}],
        "3": [{"text": "List key terms.", "page_url": "u3", "confidence": "high"}],
    }
    manifest["modules"] = [
        {"id": 1, "position": 1, "classification": "mlo_section", "overview_page_url": "u1"},
        {"id": 2, "position": 2, "classification": "mlo_section", "overview_page_url": "u2"},
        {"id": 3, "position": 3, "classification": "mlo_section", "overview_page_url": "u3"},
    ]

    report_path = alignment_skill.run_report("123", _write_manifest(tmp_path, manifest))
    report = report_path.read_text(encoding="utf-8")

    assert "Bloom Progression" in report
    assert "Module 3 Bloom ceiling lower than expected at this course position." in report
    assert "alignment.M3.bloom-regression" in (
        tmp_path / "courses" / "123" / "staging" / "alignment" / "remediation_brief.json"
    ).read_text(encoding="utf-8")
