from abstra_internals.repositories.linter.rules.css_syntax import CssSyntax
from tests.fixtures import BaseTest


class CssSyntaxTest(BaseTest):
    def test_no_css_files(self):
        rule = CssSyntax()
        self.assertEqual(len(rule.find_issues()), 0)

    def test_valid_css(self):
        (self.root / "style.css").write_text("body { color: red; margin: 0; }")
        rule = CssSyntax()
        self.assertEqual(len(rule.find_issues()), 0)

    def test_valid_css_with_at_rules(self):
        (self.root / "style.css").write_text(
            "@media (max-width: 600px) { body { color: blue; } }"
        )
        rule = CssSyntax()
        self.assertEqual(len(rule.find_issues()), 0)

    def test_valid_page_at_rule_is_not_flagged(self):
        # Regression: @page wraps declarations, not nested rules. Parsing its
        # body as a rule list produced a phantom "EOF reached before {} block
        # for a qualified rule" on this exact, valid CSS.
        (self.root / "print.css").write_text(
            "@page {\n  size: 16in 9in;\n  margin: 0;\n}"
        )
        rule = CssSyntax()
        self.assertEqual(rule.find_issues(), [])

    def test_valid_declaration_body_at_rules_are_not_flagged(self):
        # @font-face / @counter-style / @property / @viewport all wrap a
        # declaration block; none should be reported as a syntax error.
        (self.root / "faces.css").write_text(
            "@font-face { font-family: X; src: url(a.woff); }\n"
            "@counter-style thumbs { system: cyclic; symbols: '\\1F44D'; }\n"
            "@property --x { syntax: '<color>'; inherits: false; }\n"
            "@viewport { width: device-width; }"
        )
        rule = CssSyntax()
        self.assertEqual(rule.find_issues(), [])

    def test_valid_grouping_and_keyframes_at_rules_are_not_flagged(self):
        # Grouping at-rules (rule bodies) and @keyframes (keyframe-block bodies)
        # must keep parsing cleanly after the fix.
        (self.root / "anim.css").write_text(
            "@supports (display: grid) { .x { display: grid; } }\n"
            "@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n"
            "@media print { .y { color: red; } }"
        )
        rule = CssSyntax()
        self.assertEqual(rule.find_issues(), [])

    def test_real_error_inside_page_at_rule_is_still_caught(self):
        # The fix must not silence genuine declaration errors inside @page.
        (self.root / "bad-page.css").write_text("@page { : red; }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertGreater(len(issues), 0)
        self.assertIn("bad-page.css", issues[0].label)

    def test_real_error_inside_grouping_at_rule_is_still_caught(self):
        (self.root / "bad-media.css").write_text("@media print { body { color red; } }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertGreater(len(issues), 0)
        self.assertIn("bad-media.css", issues[0].label)

    def test_invalid_declaration_missing_name(self):
        (self.root / "bad.css").write_text("body { : red; }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertGreater(len(issues), 0)
        self.assertIn("bad.css", issues[0].label)

    def test_invalid_declaration_missing_colon(self):
        (self.root / "bad.css").write_text("body { color red; }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertGreater(len(issues), 0)
        self.assertIn("bad.css", issues[0].label)

    def test_empty_file(self):
        (self.root / "empty.css").touch()
        rule = CssSyntax()
        self.assertEqual(len(rule.find_issues()), 0)

    def test_multiple_files_mixed(self):
        (self.root / "good.css").write_text("body { color: red; }")
        (self.root / "bad.css").write_text("body { : red; }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertGreater(len(issues), 0)
        issue_labels = [issue.label for issue in issues]
        self.assertTrue(any("bad.css" in label for label in issue_labels))
        self.assertFalse(any("good.css" in label for label in issue_labels))

    def test_multiple_errors_single_issue(self):
        (self.root / "bad.css").write_text("body { : red; color red; }")
        rule = CssSyntax()
        issues = rule.find_issues()
        self.assertEqual(len(issues), 1)
        self.assertIn("bad.css", issues[0].label)
        self.assertGreater(issues[0].label.count("  - "), 1)

    def test_non_utf8_file_is_skipped(self):
        (self.root / "binary.css").write_bytes(b"\x80\x81\x82\x83")
        rule = CssSyntax()
        self.assertEqual(len(rule.find_issues()), 0)
