"""Tests for audit_image_alt.py — no network, no vision API calls.

Covers:
- verdict() matrix (all 7 cells)
- extract_images() parsing
- classify_heuristic() cases
- filename regex true/false cases
"""

import sys
import os
import re

# Add scripts dir so audit_image_alt can be imported without canvas_api env
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))

import audit_image_alt as ait
from audit_image_alt import (
    ImageRecord,
    verdict,
    extract_images,
    classify_heuristic,
    _is_filename_like,
    _FILENAME_RE,
    RELEVANT,
    DECORATIVE,
    VERDICT_PASS,
    VERDICT_ERROR,
    VERDICT_WARN,
    VERDICT_INFO,
)


# ── Helper ─────────────────────────────────────────────────────────────────

def make_rec(**kwargs) -> ImageRecord:
    defaults = {
        "image_id": "test-id",
        "object_type": "page",
        "object_id": "test-page",
        "page_url": "https://canvas.asu.edu/courses/1/pages/test",
        "src": "https://canvas.asu.edu/courses/1/files/1/preview",
        "alt": None,
        "role": "",
        "aria_hidden": False,
        "width": None,
        "height": None,
        "in_link": False,
        "in_figure": False,
        "has_figcaption": False,
        "context_text": "",
        "is_equation": False,
        "is_data_uri": False,
    }
    defaults.update(kwargs)
    return ImageRecord(**defaults)


# ── Verdict matrix tests ───────────────────────────────────────────────────

class TestVerdictMatrix:
    """The full verdict matrix per spec §Hardened Spec (Opus) §2."""

    # Relevant + missing alt → ERROR
    def test_relevant_missing_alt_is_error(self):
        rec = make_rec(alt=None)
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_ERROR
        assert v["severity"] == "error"
        assert v["proposed_alt"] is not None

    # Relevant + filename-like alt → ERROR
    def test_relevant_filename_alt_is_error(self):
        rec = make_rec(alt="outlier_example_f2.png")
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_ERROR
        assert v["severity"] == "error"

    # Relevant + raw LaTeX: alt → WARN
    def test_relevant_latex_alt_is_warn(self):
        rec = make_rec(alt="LaTeX: \\frac{a}{b}")
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_WARN
        assert v["severity"] == "warning"

    # Relevant + marked decorative (alt="") → ERROR
    def test_relevant_marked_decorative_alt_empty_is_error(self):
        rec = make_rec(alt="")
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_ERROR
        assert v["severity"] == "error"
        assert "hidden from AT" in v["message"]

    # Relevant + marked decorative (role=presentation) → ERROR
    def test_relevant_marked_decorative_role_is_error(self):
        rec = make_rec(alt="A bar chart", role="presentation")
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_ERROR

    # Relevant + marked decorative (aria-hidden) → ERROR
    def test_relevant_marked_decorative_aria_hidden_is_error(self):
        rec = make_rec(alt="Figure 3", aria_hidden=True)
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_ERROR

    # Decorative + alt="" + role=presentation → pass
    def test_decorative_correct_markup_is_pass(self):
        rec = make_rec(alt="", role="presentation")
        v = verdict(rec, DECORATIVE)
        assert v["code"] == VERDICT_PASS

    # Decorative + alt="" + aria-hidden → pass
    def test_decorative_aria_hidden_is_pass(self):
        rec = make_rec(alt="", aria_hidden=True)
        v = verdict(rec, DECORATIVE)
        assert v["code"] == VERDICT_PASS

    # Decorative + descriptive alt → INFO
    def test_decorative_descriptive_alt_is_info(self):
        rec = make_rec(alt="A photo of the ASU campus in spring")
        v = verdict(rec, DECORATIVE)
        assert v["code"] == VERDICT_INFO
        assert v["severity"] == "info"

    # Decorative + alt missing (not marked) → WARN
    def test_decorative_missing_alt_is_warn(self):
        rec = make_rec(alt=None)
        v = verdict(rec, DECORATIVE)
        assert v["code"] == VERDICT_WARN
        assert v["severity"] == "warning"

    # Relevant + good descriptive alt → pass
    def test_relevant_descriptive_alt_is_pass(self):
        rec = make_rec(alt="Bar chart showing grade distribution across five modules")
        v = verdict(rec, RELEVANT)
        assert v["code"] == VERDICT_PASS


# ── extract_images tests ───────────────────────────────────────────────────

class TestExtractImages:

    def _extract(self, html, **kwargs):
        return extract_images("1", "page", "test-page", html,
                              page_url="https://canvas.asu.edu/courses/1/pages/test")

    def test_alt_absent(self):
        recs = self._extract('<img src="foo.png">')
        assert len(recs) == 1
        assert recs[0]["alt"] is None  # attribute absent

    def test_alt_empty_string(self):
        recs = self._extract('<img src="foo.png" alt="">')
        assert len(recs) == 1
        assert recs[0]["alt"] == ""  # empty string, NOT None

    def test_alt_value(self):
        recs = self._extract('<img src="foo.png" alt="A descriptive alt text">')
        assert recs[0]["alt"] == "A descriptive alt text"

    def test_role_presentation(self):
        recs = self._extract('<img src="foo.png" alt="" role="presentation">')
        assert recs[0]["role"] == "presentation"

    def test_aria_hidden_true(self):
        recs = self._extract('<img src="foo.png" aria-hidden="true">')
        assert recs[0]["aria_hidden"] is True

    def test_aria_hidden_false(self):
        recs = self._extract('<img src="foo.png" aria-hidden="false">')
        assert recs[0]["aria_hidden"] is False

    def test_width_height(self):
        recs = self._extract('<img src="x.png" width="200" height="150">')
        assert recs[0]["width"] == 200
        assert recs[0]["height"] == 150

    def test_in_link(self):
        recs = self._extract('<a href="/foo"><img src="bar.png" alt=""></a>')
        assert recs[0]["in_link"] is True

    def test_not_in_link(self):
        recs = self._extract('<p><img src="bar.png" alt=""></p>')
        assert recs[0]["in_link"] is False

    def test_in_figure(self):
        recs = self._extract('<figure><img src="fig.png" alt="A chart"><figcaption>Figure 1</figcaption></figure>')
        assert recs[0]["in_figure"] is True

    def test_has_figcaption(self):
        recs = self._extract('<figure><img src="f.png" alt=""><figcaption>Caption</figcaption></figure>')
        assert recs[0]["has_figcaption"] is True

    def test_equation_src(self):
        recs = self._extract('<img src="https://canvas.asu.edu/equation_images/x%5E2" alt="x^2">')
        assert recs[0]["is_equation"] is True

    def test_equation_alt(self):
        recs = self._extract('<img src="eq.png" alt="LaTeX: \\frac{a}{b}">')
        assert recs[0]["is_equation"] is True

    def test_data_uri(self):
        recs = self._extract('<img src="data:image/png;base64,abc123" alt="">')
        assert recs[0]["is_data_uri"] is True

    def test_multiple_images(self):
        html = '<img src="a.png"><img src="b.png" alt="B">'
        recs = self._extract(html)
        assert len(recs) == 2

    def test_empty_html(self):
        recs = self._extract("")
        assert recs == []

    def test_quiz_answer_images(self):
        """Test extraction from answer-level HTML (simulated by caller passing answer HTML)."""
        ans_html = '<img src="https://canvas.asu.edu/courses/1/files/99/preview" alt="Option A diagram">'
        recs = extract_images("1", "quiz", "555", ans_html,
                              page_url="https://canvas.asu.edu/courses/1/quizzes/555")
        assert len(recs) == 1
        assert recs[0]["object_type"] == "quiz"
        assert recs[0]["alt"] == "Option A diagram"

    def test_image_id_stable(self):
        """Same inputs produce the same image_id."""
        recs1 = self._extract('<img src="foo.png" alt="desc">')
        recs2 = self._extract('<img src="foo.png" alt="desc">')
        assert recs1[0]["image_id"] == recs2[0]["image_id"]


# ── classify_heuristic tests ───────────────────────────────────────────────

class TestClassifyHeuristic:

    def test_equation_image_is_relevant(self):
        rec = make_rec(src="https://canvas.asu.edu/equation_images/x", is_equation=True)
        assert classify_heuristic(rec) == RELEVANT

    def test_equation_via_alt_is_relevant(self):
        rec = make_rec(alt="LaTeX: x^2", is_equation=True)
        assert classify_heuristic(rec) == RELEVANT

    def test_tiny_width_is_decorative(self):
        rec = make_rec(width=1, height=1)
        assert classify_heuristic(rec) == DECORATIVE

    def test_tiny_height_only_is_decorative(self):
        rec = make_rec(width=None, height=16)
        assert classify_heuristic(rec) == DECORATIVE

    def test_exactly_16px_is_decorative(self):
        rec = make_rec(width=16, height=16)
        assert classify_heuristic(rec) == DECORATIVE

    def test_17px_is_not_decorative_by_size(self):
        rec = make_rec(width=17, height=17)
        # Should not auto-classify as decorative by size alone
        result = classify_heuristic(rec)
        assert result != DECORATIVE or result is None  # 17px should need vision

    def test_aria_hidden_is_decorative(self):
        rec = make_rec(aria_hidden=True)
        assert classify_heuristic(rec) == DECORATIVE

    def test_role_presentation_is_decorative(self):
        rec = make_rec(role="presentation")
        assert classify_heuristic(rec) == DECORATIVE

    def test_normal_image_needs_vision(self):
        rec = make_rec(src="https://canvas.asu.edu/courses/1/files/99/preview",
                       alt="some alt", width=200, height=200)
        assert classify_heuristic(rec) is None

    def test_no_dims_no_role_needs_vision(self):
        rec = make_rec()
        assert classify_heuristic(rec) is None


# ── Filename regex tests ───────────────────────────────────────────────────

class TestFilenameRegex:

    TRUE_CASES = [
        "outlier_example_f2.png",
        "diagram.jpg",
        "CHART.PNG",
        "figure 3.jpeg",
        "image-final.gif",
        "logo.svg",
        "banner.webp",
        "spacer.bmp",
        "My File.jpeg",
        "  my_chart.jpg  ",   # leading/trailing whitespace
    ]

    FALSE_CASES = [
        "A bar chart showing grade distributions",
        "The equation demonstrates conservation of energy",
        "",
        "logo",          # no extension
        "image.txt",     # non-image extension
        "screenshot.pdf",
        None,
    ]

    def test_true_cases(self):
        for s in self.TRUE_CASES:
            assert _is_filename_like(s), f"Expected True for: {s!r}"

    def test_false_cases(self):
        for s in self.FALSE_CASES:
            assert not _is_filename_like(s), f"Expected False for: {s!r}"


# ── Integration smoke test ─────────────────────────────────────────────────

class TestAuditImageAltHeuristic:
    """Test audit_image_alt_heuristic with pre-fetched page data."""

    def test_missing_alt_emits_warning(self):
        pages = [{"body": '<img src="plot.png">', "url": "test-page", "html_url": "http://x/p"}]
        findings = ait.audit_image_alt_heuristic("1", pages)
        missing = [f for f in findings if f["issue_type"] == "missing_alt"]
        assert len(missing) >= 1

    def test_filename_alt_emits_warning(self):
        pages = [{"body": '<img src="plot.png" alt="plot.png">', "url": "test-page", "html_url": ""}]
        findings = ait.audit_image_alt_heuristic("1", pages)
        fname = [f for f in findings if f["issue_type"] == "filename_alt"]
        assert len(fname) >= 1

    def test_latex_alt_emits_warning(self):
        pages = [{"body": '<img src="eq.png" alt="LaTeX: x^2">', "url": "test-page", "html_url": ""}]
        findings = ait.audit_image_alt_heuristic("1", pages)
        latex = [f for f in findings if f["issue_type"] == "latex_alt"]
        assert len(latex) >= 1

    def test_good_alt_no_heuristic_finding(self):
        pages = [{"body": '<img src="chart.png" alt="A bar chart showing grade distributions">', "url": "p", "html_url": ""}]
        findings = ait.audit_image_alt_heuristic("1", pages)
        assert len(findings) == 0

    def test_equation_image_latex_alt_warning(self):
        """Equation image (heuristic=relevant) with LaTeX alt → warning."""
        pages = [{"body": '<img src="/equation_images/x%5E2" alt="LaTeX: x^2">', "url": "p", "html_url": ""}]
        findings = ait.audit_image_alt_heuristic("1", pages)
        assert any(f["issue_type"] == "latex_alt" for f in findings)

    def test_empty_pages_no_findings(self):
        findings = ait.audit_image_alt_heuristic("1", [])
        assert findings == []
