"""Tests for remediate_assess.py --with-linkage (reach + retire-candidate). No live Canvas —
canvas_api list/get methods are monkeypatched on the ra.canvas_api module object."""

import os
import sys

import pytest

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

import remediate_assess as ra


# ── Fixture course: 4 files, 2 pages, 1 assignment, 1 module item, 1 html file ──

FILES_META = [
    {"id": 101, "display_name": "A.pdf", "folder_id": 1, "url": "http://fake/101"},
    {"id": 102, "display_name": "B.pdf", "folder_id": 2, "url": "http://fake/102"},
    {"id": 103, "display_name": "widget.html", "folder_id": 3, "url": "http://fake/103"},
    {"id": 104, "display_name": "D.pdf", "folder_id": 2, "url": "http://fake/104"},
]

PAGES = [
    {"url": "page-1", "body": "<a href='/files/101/preview'>x</a> "
                              "<a href='/files/101/preview'>y</a> "
                              "<a href='/files/103'>z</a>"},
    {"url": "page-2", "body": "<p>no links here</p>"},
]

ASSIGNMENTS = [{"id": 555, "description": "<a href='/files/101/download'>dl</a>"}]

MODULES = [{"items": [{"type": "File", "content_id": 101, "id": 9001}]}]

FOLDERS = {1: "course files/Module 1", 2: "course files/templates", 3: "course files/interactives"}


def _stub_canvas_api(monkeypatch, html_bodies=None):
    monkeypatch.setattr(ra.canvas_api, "get_all_pages", lambda config, include_body=False: PAGES)
    monkeypatch.setattr(ra.canvas_api, "list_assignments", lambda config: ASSIGNMENTS)
    monkeypatch.setattr(ra.canvas_api, "list_quizzes", lambda config: [])
    monkeypatch.setattr(ra.canvas_api, "get_quiz_questions", lambda config, qid: [])
    monkeypatch.setattr(ra.canvas_api, "list_discussions", lambda config: [])
    monkeypatch.setattr(ra.canvas_api, "list_announcements", lambda config: [])
    monkeypatch.setattr(ra.canvas_api, "get_syllabus", lambda config: {"html": ""})
    monkeypatch.setattr(ra.canvas_api, "list_modules", lambda config: MODULES)
    monkeypatch.setattr(ra.canvas_api, "get_folder",
                         lambda config, fid: {"full_name": FOLDERS.get(fid)} if fid else None)
    bodies = html_bodies or {"http://fake/103": "<a href='/files/102'>see also</a>"}
    monkeypatch.setattr(ra, "_fetch_text", lambda config, url: bodies.get(url, ""))


def test_reach_counts_distinct_surfaces_and_dedups_same_body(monkeypatch):
    _stub_canvas_api(monkeypatch)
    reach, surfaces = ra.sweep_linkage(config={}, files_meta=FILES_META)

    assert len(reach[101]) == 3  # page + assignment + module-item; page double-link deduped
    assert len(reach[102]) == 1  # file-html only (from widget.html body)
    assert len(reach[103]) == 1  # page-1 links it once
    assert len(reach[104]) == 0  # linked nowhere
    assert set(surfaces) >= {"pages", "assignments", "module-items", "uploaded-html-files"}


def test_enrich_sets_reach_folder_and_retire_candidate(monkeypatch):
    _stub_canvas_api(monkeypatch)
    reach, _ = ra.sweep_linkage(config={}, files_meta=FILES_META)

    record_a = {"file": "A.pdf", "class": "digital-tagged"}
    record_b = {"file": "B.pdf", "class": "digital-untagged"}
    record_c = {"file": "widget.html", "class": "unsupported"}
    record_d = {"file": "D.pdf", "class": "digital-untagged"}
    records_with_ids = [
        (record_a, 101, 1), (record_b, 102, 2), (record_c, 103, 3), (record_d, 104, 2),
    ]

    unlinked = ra.enrich_with_linkage(records_with_ids, reach, config={})

    assert record_a["reach"] == 3
    assert record_a["retire_candidate"] is False
    assert record_a["folder"] == "course files/Module 1"

    assert record_d["reach"] == 0
    assert record_d["retire_candidate"] is True
    assert record_d["folder"] == "course files/templates"
    assert unlinked == [{"file": "D.pdf", "folder": "course files/templates", "class": "digital-untagged"}]

    # linked_from labels match the spec's example shape
    assert "assignment:555" in record_a["linked_from"]
    assert "module-item:file" in record_a["linked_from"]
    assert any(l.startswith("page:") for l in record_a["linked_from"])
    assert record_b["linked_from"] == ["file-html"]


def test_url_shape_matches_all_four_forms():
    for text in (
        "/files/123", "/files/123/download", "/files/123/preview",
        "/courses/42/files/123?wrap=1",
    ):
        assert ra._extract_file_ids(text) == {123}


def test_dedup_same_file_linked_twice_in_one_body():
    text = "<a href='/files/9/preview'>a</a> <a href='/files/9/download'>b</a>"
    assert ra._extract_file_ids(text) == {9}  # set collapses repeats regardless of surface count


def test_linkage_caveat_present_and_no_delete_verb(monkeypatch):
    _stub_canvas_api(monkeypatch)
    reach, _ = ra.sweep_linkage(config={}, files_meta=FILES_META)
    record_d = {"file": "D.pdf", "class": "digital-untagged", "route": "E2-insitu"}
    unlinked = ra.enrich_with_linkage([(record_d, 104, 2)], reach, config={})
    payload = {"records": [record_d], "unlinked": unlinked, "linkage_caveat": ra.LINKAGE_CAVEAT}

    assert payload["linkage_caveat"] == ra.LINKAGE_CAVEAT
    assert "delete" not in ra.LINKAGE_CAVEAT.lower()

    # Backlog issue text for a retire candidate must not contain a delete verb either.
    calls = []

    class _FakeBacklog:
        @staticmethod
        def add(course_id, finding):
            calls.append(finding)

    monkeypatch.setattr(ra, "backlog_manager", _FakeBacklog)
    ra._emit_backlog("999", record_d, canvas_file_id=104)
    assert calls, "backlog_manager.add was not called"
    assert "delete" not in calls[0]["issue"].lower()
    assert "review for retirement" in calls[0]["issue"]


def test_without_with_linkage_records_are_unchanged():
    # classify_file never touches reach/linked_from/folder/retire_candidate on its own.
    record = ra.classify_file("does-not-exist.pdf")
    for key in ("reach", "linked_from", "folder", "retire_candidate"):
        assert key not in record


def test_cli_with_linkage_requires_course_id(monkeypatch, capsys):
    monkeypatch.setattr(sys, "argv", ["remediate_assess.py", "--with-linkage", "--path", "x.pdf"])
    with pytest.raises(SystemExit):
        ra.main()
