import json
import os
import sys
from pathlib import Path

import pytest

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

import alignment_mode_b
import build_mockup_quiz
import canvas_api
import staging_manager
import staging_schema


COURSE_ID = "218764"
FIXTURE_DIR = Path(__file__).parent / "fixtures"


def _config(course_id: str = COURSE_ID) -> dict[str, object]:
    return {
        "token": "fake",
        "domain": "asu-dev.instructure.com",
        "course_id": course_id,
        "base_url": "https://asu-dev.instructure.com/api/v1",
        "course_url": f"https://asu-dev.instructure.com/api/v1/courses/{course_id}",
        "headers": {"Authorization": "Bearer fake"},
    }


@pytest.fixture()
def isolated_root(tmp_path, monkeypatch):
    monkeypatch.setattr(staging_manager, "get_plugin_root", lambda: str(tmp_path))
    monkeypatch.setattr(staging_schema, "get_plugin_root", lambda: str(tmp_path))
    monkeypatch.setattr(canvas_api, "get_plugin_root", lambda: str(tmp_path))
    monkeypatch.setattr(canvas_api, "get_config", lambda course_id=None: _config(str(course_id or COURSE_ID)))
    monkeypatch.setattr(canvas_api, "require_course_id", lambda config: None)
    return tmp_path


def _live_questions():
    return [
        {
            "id": 1001,
            "question_name": "Current Q1",
            "question_text": "<p>Students use the sampling distribution to solve a scenario.</p>",
            "question_type": "multiple_choice_question",
            "points_possible": 1,
            "answers": [{"answer_text": "Current answer A", "answer_weight": 100}],
        },
        {
            "id": 1002,
            "question_name": "Current Q2",
            "question_text": "<p>Students define a p-value.</p>",
            "question_type": "multiple_choice_question",
            "points_possible": 1,
            "answers": [{"answer_text": "Current answer B", "answer_weight": 100}],
        },
        {
            "id": 1003,
            "question_name": "Current Q3",
            "question_text": "<p>Placeholder current text.</p>",
            "question_type": "multiple_choice_question",
            "points_possible": 1,
            "answers": [{"answer_text": "Current answer C", "answer_weight": 100}],
        },
        {
            "id": 1004,
            "question_name": "Current Q4",
            "question_text": "<p>Students compose an interpretation.</p>",
            "question_type": "multiple_choice_question",
            "points_possible": 1,
            "answers": [{"answer_text": "Current answer D", "answer_weight": 100}],
        },
    ]


@pytest.fixture()
def staged_mockup_quiz(isolated_root, monkeypatch):
    monkeypatch.setattr(canvas_api, "get_quiz_questions", lambda config, quiz_id: _live_questions())
    result = build_mockup_quiz.build_staged_quiz(
        COURSE_ID,
        FIXTURE_DIR / "sample_quiz_decisions.yaml",
        "mockup-quiz",
    )
    assert result["ok"], result
    return staging_manager.get_staged(COURSE_ID, "quizzes", "mockup-quiz")


def test_build_mockup_quiz_keep_item_has_alignment_rationale_without_examples(staged_mockup_quiz):
    question = staged_mockup_quiz["questions"][0]
    assert "Aligns with MLO 1 because" in question["question_text"]
    assert "Example question:" not in question["question_text"]
    assert "Example option" not in question["question_text"]
    assert all("Example" not in answer["answer_text"] for answer in question["answers"])


def test_build_mockup_quiz_replace_item_has_example_question_and_answers(staged_mockup_quiz):
    question = staged_mockup_quiz["questions"][1]
    assert "Current item misaligns with MLO 2 because" in question["question_text"]
    assert "<strong>Example question:</strong>" in question["question_text"]
    assert question["question_text"].count("Example option") == 4
    assert len(question["answers"]) == 4


def test_build_mockup_quiz_add_item_gap_rationale(staged_mockup_quiz):
    question = staged_mockup_quiz["questions"][2]
    assert "Closes a gap on MLO 3 because" in question["question_text"]


def test_build_mockup_quiz_clo_only_flag_is_yellow_mark(staged_mockup_quiz):
    question = staged_mockup_quiz["questions"][3]
    assert '<mark style="background:#fff3bf;color:#191919;padding:2px 4px;">CLO-only flag:' in question["question_text"]


def test_build_mockup_quiz_output_passes_staging_schema(isolated_root, staged_mockup_quiz):
    result = staging_schema.validate(COURSE_ID, "quizzes", "mockup-quiz")
    assert result["ok"], result


def test_item_alignment_bloom_verb_inference():
    assert alignment_mode_b.infer_bloom_abbreviation("Define the sampling distribution.") == "R"
    assert alignment_mode_b.infer_bloom_abbreviation("Use the rule to solve a problem.") == "A"
    assert alignment_mode_b.infer_bloom_abbreviation("Define and solve the same item.") == "bloom_undetermined"


def test_item_alignment_flag_emission_for_all_sidebar_flags():
    course_config = {
        "clos": [{"id": "CLO1", "text": "Course outcome"}],
        "modules": [
            {
                "objectives": [
                    {"id": "MLO1", "text": "Apply the rule", "blooms": "Apply"},
                    {"id": "MLO2", "text": "Explain the concept", "blooms": "Understand"},
                ]
            }
        ],
    }
    rows = [
        {"id": "q1", "label": "Unaligned", "text": "Recall a fact."},
        {"id": "q2", "label": "Mismatch", "text": "Define MLO1.", "mlo_ids": ["MLO1"]},
        {"id": "q3", "label": "CLO only", "text": "Explain CLO1.", "clo_ids": ["CLO1"]},
        {"id": "q4", "label": "Redundant A", "text": "Apply MLO2.", "mlo_ids": ["MLO2"]},
        {"id": "q5", "label": "Redundant B", "text": "Apply MLO2.", "mlo_ids": ["MLO2"]},
        {"id": "q6", "label": "Redundant C", "text": "Apply MLO2.", "mlo_ids": ["MLO2"]},
    ]

    model = alignment_mode_b.build_item_alignment_model(course_config, rows)
    flags_by_id = {row["id"]: row["flags"] for row in model["rows"]}

    assert "unaligned" in flags_by_id["q1"]
    assert "bloom_mismatch" in flags_by_id["q2"]
    assert "clo_only" in flags_by_id["q3"]
    assert all("redundant" in flags_by_id[row_id] for row_id in ("q4", "q5", "q6"))


def test_item_alignment_report_matrix_html_structure(isolated_root, monkeypatch):
    config_dir = isolated_root / "courses" / COURSE_ID
    config_dir.mkdir(parents=True)
    (config_dir / "course-config.json").write_text(
        json.dumps({
            "clos": [{"id": "CLO1", "text": "Course outcome one"}],
            "modules": [
                {
                    "objectives": [
                        {"id": "MLO1", "text": "Apply the rule", "blooms": "Apply"},
                        {"id": "MLO2", "text": "Explain the concept", "blooms": "Understand"},
                    ],
                }
            ],
        }),
        encoding="utf-8",
    )
    monkeypatch.setattr(canvas_api, "get_quiz", lambda config, quiz_id: {"id": quiz_id, "title": "Quiz One"})
    monkeypatch.setattr(canvas_api, "get_assignment", lambda config, assignment_id: None)
    monkeypatch.setattr(canvas_api, "get_quiz_questions", lambda config, quiz_id: [
        {"id": 1, "question_name": "Question One", "question_text": "<p>Use MLO1.</p>", "mlo_ids": ["MLO1"]},
        {"id": 2, "question_name": "Question Two", "question_text": "<p>Explain CLO1.</p>", "clo_ids": ["CLO1"]},
    ])

    rc = alignment_mode_b.main([
        "--course-id", COURSE_ID,
        "--item-alignment-report", "77",
        "--dry-run",
    ])

    assert rc == 0
    reports = list((config_dir / "reports").glob("*-alignment-77.html"))
    assert len(reports) == 1
    html = reports[0].read_text(encoding="utf-8")
    assert '<th scope="col" data-objective-id="MLO1">MLO_1</th>' in html
    assert '<th scope="col" data-objective-id="CLO1">CLO_1</th>' in html
    assert '<th scope="row">Question One</th><td data-objective-id="MLO1">A</td>' in html
    assert '<th scope="row">Question Two</th><td data-objective-id="MLO1"></td><td data-objective-id="MLO2"></td><td data-objective-id="CLO1">U</td>' in html
    assert "<!-- dry_run=true -->" in html
