import os
import sys

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

import audit_mlo_overclaim
import criterion_evaluator


# ---------------------------------------------------------------------------
# audit_mlo_overclaim unit tests
# ---------------------------------------------------------------------------

def test_no_findings_for_evidenced_assessment():
    """Module with both MLOs and assessments should produce no findings."""
    config = {
        "modules": [{
            "number": 1,
            "title": "Module 1",
            "objectives": [{"id": "M1.1", "text": "Apply legal analysis."}],
            "assessments": {"artifact": {"title": "Case Brief", "points": 30}},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    assert findings == []


def test_flags_empty_mlo_module_with_assessment():
    """Module with assessments but no MLOs should be flagged as overclaim_empty_mlos."""
    config = {
        "modules": [{
            "number": 2,
            "title": "Module 2: Analysis",
            "objectives": [],
            "assessments": {"knowledge_check": {"title": "KC", "points": 10}},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    assert len(findings) == 1
    f = findings[0]
    assert f["issue_type"] == "overclaim_empty_mlos"
    assert f["module_id"] == 2
    assert "Module 2" in f["module_name"]
    assert "no declared" in f["detail"].lower() or "not been defined" in f["detail"]


def test_no_finding_when_module_has_no_assessments():
    """Module with no assessments and no MLOs should not be flagged."""
    config = {
        "modules": [{
            "number": 3,
            "title": "Module 3: Intro",
            "objectives": [],
            "assessments": {},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    assert findings == []


def test_flags_cross_module_mlo_reference():
    """Assessment explicitly declaring an MLO from another module is flagged."""
    config = {
        "modules": [
            {
                "number": 1,
                "title": "Module 1",
                "objectives": [{"id": "M1.1", "text": "Define terms."}],
                "assessments": [],
            },
            {
                "number": 2,
                "title": "Module 2",
                "objectives": [{"id": "M2.1", "text": "Apply rules."}],
                # Assessment in Module 2 explicitly declares M1.1 (cross-module)
                "assessments": [
                    {
                        "id": "asmt-m2-quiz",
                        "title": "Module 2 Quiz",
                        "type": "quiz",
                        "mlo_ids": ["M1.1"],   # cross-module reference
                    }
                ],
            },
        ],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    cross = [f for f in findings if f["issue_type"] == "overclaim_cross_module"]
    assert len(cross) == 1
    assert cross[0]["mlo_id"] == "M1.1"
    assert cross[0]["module_id"] == 2
    assert "M1.1" in cross[0]["detail"]


def test_no_cross_module_flag_for_same_module_mlo():
    """Assessment declaring an MLO from its own module should not be flagged."""
    config = {
        "modules": [{
            "number": 1,
            "title": "Module 1",
            "objectives": [{"id": "M1.1", "text": "Analyze statutes."}],
            "assessments": [
                {
                    "id": "quiz-m1",
                    "title": "Module 1 Quiz",
                    "type": "quiz",
                    "mlo_ids": ["M1.1"],   # same module -- OK
                }
            ],
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    cross = [f for f in findings if f["issue_type"] == "overclaim_cross_module"]
    assert cross == []


def test_empty_config_produces_no_findings():
    """Empty config should return empty findings without error."""
    assert audit_mlo_overclaim.audit_mlo_overclaim(course_config={}) == []


# ---------------------------------------------------------------------------
# Fix 1: MLO field name coverage (mlos_draft + learning_objectives)
# ---------------------------------------------------------------------------

def test_mlos_draft_field_not_flagged_as_empty_mlos():
    """Module declaring MLOs via mlos_draft (dict-items) must NOT produce
    a false-positive overclaim_empty_mlos finding."""
    config = {
        "modules": [{
            "number": 1,
            "title": "Module 1",
            # mlos_draft is a valid MLO field used by several course configs
            # (e.g., courses/256173/course-config.json)
            "mlos_draft": [
                {"id": "MLO1.1", "text": "Distinguish expectation damages.", "bloom": "Understand"},
                {"id": "MLO1.2", "text": "Apply Hadley v. Baxendale foreseeability rule.", "bloom": "Apply"},
            ],
            "assessments": {"artifact": {"title": "Damages Brief", "points": 40}},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    empty_mlo_findings = [f for f in findings if f["issue_type"] == "overclaim_empty_mlos"]
    assert empty_mlo_findings == [], (
        f"False-positive overclaim_empty_mlos on module using mlos_draft: {empty_mlo_findings}"
    )


def test_learning_objectives_field_not_flagged_as_empty_mlos():
    """Module declaring MLOs via learning_objectives (dict-items) must NOT produce
    a false-positive overclaim_empty_mlos finding."""
    config = {
        "modules": [{
            "number": 2,
            "title": "Module 2",
            "learning_objectives": [
                {"id": "M2.1", "text": "Analyze contract formation elements."},
            ],
            "assessments": {"knowledge_check": {"title": "Formation KC", "points": 10}},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    empty_mlo_findings = [f for f in findings if f["issue_type"] == "overclaim_empty_mlos"]
    assert empty_mlo_findings == [], (
        f"False-positive overclaim_empty_mlos on module using learning_objectives: {empty_mlo_findings}"
    )


def test_mlos_plain_strings_not_flagged_as_empty_mlos():
    """Module declaring MLOs via plain-string mlos list (as in courses/255724)
    must NOT produce a false-positive overclaim_empty_mlos finding."""
    config = {
        "modules": [{
            "number": 3,
            "title": "Module 3",
            # Plain string MLOs — the format used in courses/255724/course-config.json
            "mlos": [
                "Analyze a legal problem to determine relevant legal and factual issues",
                "Apply basic case assessment methods to evaluate strengths and weaknesses",
            ],
            "assessments": {"artifact": {"title": "Case Assessment Memo", "points": 30}},
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    empty_mlo_findings = [f for f in findings if f["issue_type"] == "overclaim_empty_mlos"]
    assert empty_mlo_findings == [], (
        f"False-positive overclaim_empty_mlos on module using plain-string mlos: {empty_mlo_findings}"
    )


# ---------------------------------------------------------------------------
# Fix 2: dict-shaped assessments with mlo_ids (cross-module over-claim)
# ---------------------------------------------------------------------------

def test_dict_shaped_assessments_cross_module_overclaim():
    """Dict-shaped assessments config (assessments.knowledge_check, etc.) with
    an explicit mlo_ids referencing an MLO from another module IS flagged."""
    config = {
        "modules": [
            {
                "number": 1,
                "title": "Module 1",
                "objectives": [{"id": "M1.1", "text": "Define negligence elements."}],
                "assessments": [],
            },
            {
                "number": 2,
                "title": "Module 2",
                "objectives": [{"id": "M2.1", "text": "Apply duty of care standard."}],
                # Dict-shaped assessments — previously only list-shape was checked
                "assessments": {
                    "knowledge_check": {
                        "title": "Negligence KC",
                        "points": 10,
                        "mlo_ids": ["M1.1"],  # cross-module reference
                    },
                    "artifact": {
                        "title": "Duty Analysis",
                        "points": 30,
                        # no mlo_ids — should not be flagged
                    },
                },
            },
        ],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    cross = [f for f in findings if f["issue_type"] == "overclaim_cross_module"]
    assert len(cross) == 1, f"Expected 1 cross-module finding, got {len(cross)}: {cross}"
    assert cross[0]["mlo_id"] == "M1.1"
    assert cross[0]["module_id"] == 2
    assert "M1.1" in cross[0]["detail"]


def test_dict_shaped_assessments_same_module_not_flagged():
    """Dict-shaped assessments config with mlo_ids referencing the module's own
    MLO must NOT be flagged."""
    config = {
        "modules": [{
            "number": 1,
            "title": "Module 1",
            "objectives": [{"id": "M1.1", "text": "Analyze contract terms."}],
            "assessments": {
                "artifact": {
                    "title": "Contract Analysis",
                    "points": 40,
                    "mlo_ids": ["M1.1"],  # same module — OK
                },
            },
        }],
    }
    findings = audit_mlo_overclaim.audit_mlo_overclaim(course_config=config)
    cross = [f for f in findings if f["issue_type"] == "overclaim_cross_module"]
    assert cross == [], f"False-positive cross-module finding: {cross}"


# ---------------------------------------------------------------------------
# criterion_evaluator integration: mlo_overclaim_findings wired in
# ---------------------------------------------------------------------------

def test_criterion_evaluator_collects_mlo_overclaim_key(monkeypatch):
    """_collect_phase2_findings should include mlo_overclaim_findings key."""
    monkeypatch.setattr(criterion_evaluator.audit_rubrics, "audit_rubrics", lambda **_: [])
    monkeypatch.setattr(criterion_evaluator.audit_mlo_drift, "audit_mlo_drift", lambda **_: [])
    monkeypatch.setattr(criterion_evaluator.audit_bloom_alignment, "audit_bloom_alignment", lambda **_: [])
    monkeypatch.setattr(criterion_evaluator.audit_clo_orphans, "audit_clo_orphans", lambda **_: [])
    monkeypatch.setattr(
        criterion_evaluator.audit_mlo_overclaim,
        "audit_mlo_overclaim",
        lambda **_: [{"issue_type": "overclaim_empty_mlos", "detail": "No MLOs defined."}],
    )

    added = []
    monkeypatch.setattr(
        criterion_evaluator.backlog_manager,
        "add",
        lambda course_id, item: added.append((course_id, item)) or item,
    )

    findings = criterion_evaluator._collect_phase2_findings({"course_id": "88881", "assignments": []})
    result = criterion_evaluator._append_phase2_backlog_items("88881", findings)

    assert "mlo_overclaim_findings" in findings
    assert len(findings["mlo_overclaim_findings"]) == 1
    # One backlog item should have been emitted for the overclaim finding
    overclaim_items = [
        item for _, item in added
        if item.get("source") == "audit_mlo_overclaim.py"
    ]
    assert len(overclaim_items) == 1
    assert overclaim_items[0]["issue_type"] == "overclaim_empty_mlos"
