"""Tests for scripts/content_remediation.py.

All tests are token-less (no Canvas API calls). Run with:
    pytest tests/test_content_remediation.py -q
"""

import pathlib
import sys

SCRIPTS = pathlib.Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS))

import importlib.util as _ilu

def _load_apa():
    spec = _ilu.spec_from_file_location("apa_title_case", SCRIPTS / "apa_title_case.py")
    mod = _ilu.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

from content_remediation import (
    fix_dashes,
    fix_heading_title_case,
    fix_table_row_background,
    fix_ai_slop_binary_contrast,
    EM_DASH,
    EN_DASH,
    MINUS,
    HYPHEN,
    _DASH_TOKENS,
)


# ===========================================================================
# fix_dashes
# ===========================================================================

class TestFixDashes:
    def test_em_dash_in_heading_becomes_colon(self):
        html = "<h2>Risk Management—Key Principles</h2>"
        result, n = fix_dashes(html)
        assert "—" not in result
        assert ": " in result
        assert n >= 1

    def test_en_dash_in_heading_becomes_colon(self):
        html = "<h3>Module 1–Overview</h3>"
        result, n = fix_dashes(html)
        assert "–" not in result
        assert ": " in result or ": " in result
        assert n >= 1

    def test_heading_with_existing_colon_uses_comma(self):
        # Heading already has a colon: dash should become comma, not double-colon.
        html = "<h2>Lesson 0.8: Long and Short—One Skill</h2>"
        result, n = fix_dashes(html)
        assert "—" not in result
        assert result.count(":") == 1, "Should not add a second colon"
        assert ", " in result
        assert n >= 1

    def test_body_em_dash_becomes_comma(self):
        html = "<p>The price rose — then fell.</p>"
        result, n = fix_dashes(html)
        assert "—" not in result
        assert ", " in result
        assert n >= 1

    def test_body_en_dash_becomes_comma(self):
        html = "<p>Pages 10–20 are required.</p>"
        result, n = fix_dashes(html)
        assert "–" not in result
        assert n >= 1

    def test_mdash_entity_replaced(self):
        html = "<p>Hello &mdash; world.</p>"
        result, n = fix_dashes(html)
        assert "&mdash;" not in result
        assert n >= 1

    def test_ndash_entity_replaced(self):
        html = "<p>Hello &ndash; world.</p>"
        result, n = fix_dashes(html)
        assert "&ndash;" not in result
        assert n >= 1

    def test_minus_u2212_preserved(self):
        # U+2212 minus must NEVER be touched.
        html = "<p>The return is −2R and −3%.</p>"
        result, n = fix_dashes(html)
        assert result.count(MINUS) == 2, "Both minus signs must be preserved"
        assert n == 0

    def test_hyphen_u002d_preserved(self):
        html = "<p>risk:reward ratio and hard-to-borrow stocks.</p>"
        result, n = fix_dashes(html)
        assert "hard-to-borrow" in result
        assert "risk:reward" in result
        assert n == 0

    def test_price_range_hyphen_preserved(self):
        html = "<p>The range is $64,191-$64,349 today.</p>"
        result, n = fix_dashes(html)
        assert "$64,191-$64,349" in result
        assert n == 0

    def test_zero_changes_on_clean_html(self):
        html = "<p>No dashes here, just plain text.</p>"
        _, n = fix_dashes(html)
        assert n == 0

    def test_idempotent(self):
        html = "<h2>Key Concepts—Applied</h2><p>The test — passed.</p>"
        result1, n1 = fix_dashes(html)
        result2, n2 = fix_dashes(result1)
        assert n2 == 0, "Second pass must produce 0 changes"
        assert result1 == result2

    def test_zero_dashes_remain_post_condition(self):
        html = "<h2>A—B</h2><p>C–D</p><p>E&mdash;F</p><p>G&ndash;H</p>"
        result, _ = fix_dashes(html)
        for tok in _DASH_TOKENS:
            assert tok not in result, f"Token {tok!r} still present in output"

    def test_empty_string(self):
        result, n = fix_dashes("")
        assert result == ""
        assert n == 0

    def test_multiple_dashes_counted(self):
        html = "<p>A—B–C—D</p>"
        _, n = fix_dashes(html)
        assert n == 3


# ===========================================================================
# fix_heading_title_case
# ===========================================================================

class TestFixHeadingTitleCase:
    def test_article_lowercased_mid_title(self):
        html = "<h2>The Impact Of The Technology</h2>"
        result, n = fix_heading_title_case(html)
        # "The" at start stays; "Of" and second "The" lowercased if minor word
        assert "Of" not in result or result.index("Of") == 0  # "of" mid-title
        assert n >= 1 or result == html  # may or may not change depending on "Of"

    def test_minor_words_lowercased(self):
        html = "<h2>The Rise Of Administrative Law</h2>"
        result, n = fix_heading_title_case(html)
        # "Of" should become "of"
        assert "Of" not in result

    def test_first_word_always_capitalized(self):
        html = "<h2>the rise of administrative law</h2>"
        result, n = fix_heading_title_case(html)
        # First word "the" must be capitalized
        assert result.startswith("<h2>The") or "The" in result

    def test_last_word_always_capitalized(self):
        # "the" at end should be capitalized
        html = "<h2>Introduction to the</h2>"
        result, n = fix_heading_title_case(html)
        text = result.replace("<h2>", "").replace("</h2>", "")
        last_word = text.strip().split()[-1]
        assert last_word[0].isupper()

    def test_no_change_on_correct_title(self):
        html = "<h2>The Rise of Administrative Law</h2>"
        _, n = fix_heading_title_case(html)
        assert n == 0

    def test_idempotent(self):
        html = "<h2>the Impact Of Technology And Law</h2>"
        result1, n1 = fix_heading_title_case(html)
        result2, n2 = fix_heading_title_case(result1)
        assert n2 == 0
        assert result1 == result2

    def test_empty_string(self):
        result, n = fix_heading_title_case("")
        assert result == ""
        assert n == 0

    def test_non_heading_text_unchanged(self):
        html = "<p>the quick brown fox</p>"
        result, n = fix_heading_title_case(html)
        assert result == html
        assert n == 0

    def test_only_h1_to_h4_affected_not_h5(self):
        html = "<h5>the quick fox</h5>"
        result, n = fix_heading_title_case(html)
        assert result == html
        assert n == 0

    def test_after_colon_word_capitalized(self):
        """Word right after a colon must be capitalized (BUG 1 regression)."""
        html = "<h2>Risk: a Primer</h2>"
        result, n = fix_heading_title_case(html)
        assert result == "<h2>Risk: A Primer</h2>", repr(result)

    def test_em_dash_to_colon_then_minor_word_capitalized(self):
        """After fix_dashes converts — to :, the following minor word stays cap."""
        html = "<h2>Lesson 5 — A Guide To The Markets</h2>"
        after_dashes, _ = fix_dashes(html)
        assert after_dashes == "<h2>Lesson 5: A Guide To The Markets</h2>", repr(after_dashes)
        result, n = fix_heading_title_case(after_dashes)
        assert result == "<h2>Lesson 5: A Guide to the Markets</h2>", repr(result)
        # Also verify apa_title_case linter agrees: zero issues.
        apa = _load_apa()
        issues = apa.check_title("Lesson 5: A Guide to the Markets")
        assert issues == [], f"Residual title-case issues: {issues}"

    def test_acronym_preserved(self):
        """All-caps tokens (VWAP, US) must not be lowercased mid-title."""
        html = "<h3>The VWAP And US Rules</h3>"
        result, n = fix_heading_title_case(html)
        assert "VWAP" in result, repr(result)
        assert "US" in result, repr(result)
        assert "and" in result, repr(result)  # "And" becomes "and"


# ===========================================================================
# fix_table_row_background
# ===========================================================================

class TestFixTableRowBackground:
    def _make_table_html(self, tr_style, cell_content="<td>Cell</td>"):
        return f'<table><tr style="{tr_style}">{cell_content}</tr></table>'

    def test_background_moved_to_cells(self):
        html = self._make_table_html("background-color: #8C1D40; color: white;")
        result, n = fix_table_row_background(html)
        assert n == 1
        # Background should appear on cell, not on tr.
        assert "background-color: #8C1D40" in result
        # tr style should have bg removed (rest_part empty here).
        # The <td> should carry the background.
        assert '<td style="' in result or '<td style=' in result

    def test_transparent_background_ignored(self):
        html = self._make_table_html("background-color: transparent;")
        result, n = fix_table_row_background(html)
        assert n == 0
        assert result == html

    def test_no_background_no_change(self):
        html = self._make_table_html("color: red;")
        result, n = fix_table_row_background(html)
        assert n == 0
        assert result == html

    def test_cells_already_have_bg_skipped(self):
        html = '<table><tr style="background-color: blue;"><td style="background-color: blue;">X</td></tr></table>'
        result, n = fix_table_row_background(html)
        assert n == 0
        assert result == html

    def test_idempotent(self):
        html = self._make_table_html("background-color: #8C1D40; color: white;")
        result1, n1 = fix_table_row_background(html)
        result2, n2 = fix_table_row_background(result1)
        assert n2 == 0
        assert result1 == result2

    def test_other_tr_styles_preserved(self):
        html = self._make_table_html("font-weight: bold; background-color: #333;")
        result, n = fix_table_row_background(html)
        assert n == 1
        # font-weight should still be on tr
        assert "font-weight: bold" in result

    def test_empty_string(self):
        result, n = fix_table_row_background("")
        assert result == ""
        assert n == 0

    def test_no_table_no_change(self):
        html = "<p>No table here.</p>"
        result, n = fix_table_row_background(html)
        assert n == 0
        assert result == html

    def test_empty_style_attr_removed_from_tr(self):
        """After moving bg off tr when no other styles remain, style="" must not appear."""
        html = '<table><tr style="background-color: #8C1D40; color: white;"><td>Cell</td></tr></table>'
        result, n = fix_table_row_background(html)
        assert n == 1
        assert 'style=""' not in result, repr(result)
        assert "style=''" not in result, repr(result)
        # The <tr> should now be a bare <tr> with no style attribute.
        assert "<tr>" in result or "<tr " in result.replace("<tr>", "")

    def test_empty_style_idempotent(self):
        """Running fix twice on an already-fixed file produces 0 changes."""
        html = '<table><tr style="background-color: #8C1D40;"><td>Cell</td></tr></table>'
        result1, n1 = fix_table_row_background(html)
        assert n1 == 1
        result2, n2 = fix_table_row_background(result1)
        assert n2 == 0, f"Second pass made {n2} changes on: {repr(result1)}"
        assert result1 == result2


# ===========================================================================
# fix_ai_slop_binary_contrast — no-op
# ===========================================================================

class TestFixAiSlopBinaryContrast:
    def test_returns_html_unchanged(self):
        html = "<p>Not X, but Y is the answer.</p>"
        result, n = fix_ai_slop_binary_contrast(html)
        assert result == html
        assert n == 0

    def test_empty_string(self):
        result, n = fix_ai_slop_binary_contrast("")
        assert result == ""
        assert n == 0

    def test_any_input_returns_zero_count(self):
        for html in [
            "<h2>Not speed, but accuracy</h2>",
            "<p>isn't about theory, it's about practice</p>",
            "",
            "plain text",
        ]:
            result, n = fix_ai_slop_binary_contrast(html)
            assert n == 0
            assert result == html
