"""Tests for scripts/wcag_checks.py (PR1 extraction).

Guards the WCAG validator extraction from check_interactive_a11y.py:
- byte-for-byte: interactive gate output unchanged after the move
- branch-complete: every failure branch in the stateful parser fires
- no-import-cycle: wcag_checks does not import check_interactive_a11y
"""

import ast
import os
import sys
import unittest
from pathlib import Path

HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPTS = os.path.join(HERE, "..", "scripts")
sys.path.insert(0, SCRIPTS)

import wcag_checks  # noqa: E402
import check_interactive_a11y  # noqa: E402


def _run_parser(html, path="x.html"):
    p = wcag_checks.WCAGContentParser(path)
    p.feed(html)
    p.close()
    p.finalize()
    return p


def _codes(findings):
    return sorted(f["code"] for f in findings)


class NoImportCycleTest(unittest.TestCase):
    def test_wcag_checks_does_not_import_interactive_gate(self):
        src = Path(SCRIPTS, "wcag_checks.py").read_text(encoding="utf-8")
        tree = ast.parse(src)
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    self.assertNotIn("check_interactive_a11y", alias.name)
            if isinstance(node, ast.ImportFrom):
                self.assertNotEqual(node.module, "check_interactive_a11y")

    def test_interactive_gate_imports_from_wcag_checks(self):
        # The gate must source its validators from the shared module.
        self.assertIs(check_interactive_a11y.InteractiveA11yParser, wcag_checks.WCAGContentParser)
        self.assertIs(check_interactive_a11y.contrast_issue, wcag_checks.contrast_issue)


class ByteForByteGateOutputTest(unittest.TestCase):
    """The interactive deploy gate report must be unchanged by the extraction."""

    def test_valid_doc_passes(self):
        html = (
            '<!doctype html><html lang="en"><head>'
            '<meta name="viewport" content="width=device-width, initial-scale=1">'
            '</head><body><h1>Title</h1><button>Start</button></body></html>'
        )
        import tempfile
        with tempfile.TemporaryDirectory() as d:
            p = Path(d, "valid.html")
            p.write_text(html, encoding="utf-8")
            report = check_interactive_a11y.run([str(p)])
        self.assertTrue(report["pass"])
        self.assertEqual(report["failures"], [])

    def test_dirty_doc_failure_codes_stable(self):
        # Missing lang + viewport + bad heading start + img-no-alt + low contrast
        html = (
            "<html><body>"
            '<img src="x.png">'
            '<h3>Skipped start</h3>'
            '<p style="color:#777;background-color:#888">low contrast</p>'
            "</body></html>"
        )
        import tempfile
        with tempfile.TemporaryDirectory() as d:
            p = Path(d, "dirty.html")
            p.write_text(html, encoding="utf-8")
            report = check_interactive_a11y.run([str(p)])
        self.assertFalse(report["pass"])
        codes = _codes(report["failures"])
        for expected in ("WCAG 1.1.1", "WCAG 1.3.1", "WCAG 1.4.3", "WCAG 1.4.10", "WCAG 3.1.1"):
            self.assertIn(expected, codes, f"{expected} missing from {codes}")


class BranchCompleteParserTest(unittest.TestCase):
    """Every failure branch in the stateful parser must be exercised."""

    def test_missing_lang(self):
        p = _run_parser('<html><head><meta name="viewport" content="x"></head><body></body></html>')
        self.assertIn("WCAG 3.1.1", _codes(p.failures))

    def test_missing_viewport(self):
        p = _run_parser('<html lang="en"><body></body></html>')
        self.assertIn("WCAG 1.4.10", _codes(p.failures))

    def test_img_without_alt(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><img src="a.png"></body></html>')
        self.assertIn("WCAG 1.1.1", _codes(p.failures))

    def test_img_decorative_passes(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><img src="a.png" role="presentation"></body></html>')
        self.assertNotIn("WCAG 1.1.1", _codes(p.failures))

    def test_heading_start_not_h1(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><h3>x</h3></body></html>')
        self.assertIn("WCAG 1.3.1", _codes(p.failures))

    def test_canvas_wrapped_h2_start_passes(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><!-- canvas-wrapped --><h2>x</h2></body></html>')
        self.assertNotIn("WCAG 1.3.1", _codes(p.failures))

    def test_heading_level_jump(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><h1>a</h1><h3>b</h3></body></html>')
        self.assertIn("WCAG 1.3.1", _codes(p.failures))

    def test_low_contrast_inline(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><p style="color:#777;background:#888">x</p></body></html>')
        self.assertIn("WCAG 1.4.3", _codes(p.failures))

    def test_bad_tabindex(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><div tabindex="3">x</div></body></html>')
        self.assertIn("WCAG 2.4.3", _codes(p.failures))

    def test_radio_without_aria_checked(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><div role="radio">x</div></body></html>')
        self.assertIn("WCAG 4.1.2", _codes(p.failures))

    def test_disallowed_tag(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><marquee>x</marquee></body></html>')
        self.assertIn("WCAG 2.2.2", _codes(p.failures))

    def test_control_without_accessible_name(self):
        p = _run_parser('<html lang="en"><head><meta name="viewport" content="x"></head><body><input type="text"></body></html>')
        self.assertIn("WCAG 4.1.2", _codes(p.failures))

    def test_control_with_associated_label_passes(self):
        html = '<html lang="en"><head><meta name="viewport" content="x"></head><body><label for="n">Name</label><input id="n" type="text"></body></html>'
        p = _run_parser(html)
        self.assertNotIn("WCAG 4.1.2", _codes(p.failures))

    def test_control_with_aria_label_passes(self):
        html = '<html lang="en"><head><meta name="viewport" content="x"></head><body><input type="text" aria-label="Name"></body></html>'
        p = _run_parser(html)
        self.assertNotIn("WCAG 4.1.2", _codes(p.failures))

    def test_button_with_text_passes(self):
        html = '<html lang="en"><head><meta name="viewport" content="x"></head><body><button>Submit</button></body></html>'
        p = _run_parser(html)
        self.assertNotIn("WCAG 4.1.2", _codes(p.failures))

    def test_btn_disabled_warning(self):
        html = '<html lang="en"><head><meta name="viewport" content="x"></head><body><button class="btn-disabled" aria-label="x">y</button></body></html>'
        p = _run_parser(html)
        self.assertIn("WARNING", _codes(p.warnings))


class ContrastHelperTest(unittest.TestCase):
    def test_contrast_ratio_black_white(self):
        ratio = wcag_checks.contrast_ratio((0, 0, 0), (255, 255, 255))
        self.assertAlmostEqual(ratio, 21.0, places=1)

    def test_parse_color_hex_short(self):
        self.assertEqual(wcag_checks.parse_color("#fff"), (255, 255, 255))

    def test_parse_color_rgb(self):
        self.assertEqual(wcag_checks.parse_color("rgb(10, 20, 30)"), (10, 20, 30))

    def test_contrast_issue_skips_when_color_missing(self):
        self.assertIsNone(wcag_checks.contrast_issue("color:#000"))  # no background


if __name__ == "__main__":
    unittest.main()
