"""Tests for YAML rule catalog loading and structure validation."""

from __future__ import annotations

from pathlib import Path

import yaml
import pytest

RULES_DIR = Path(__file__).resolve().parent.parent / "oncecheck" / "rules"

VALID_SEVERITIES = {"FAIL", "WARN", "INFO"}
RULE_ID_PATTERN_MAP = {
    "ios_rules.yaml": "IOS-",
    "android_rules.yaml": "AND-",
    "web_rules.yaml": "WEB-",
    "common_rules.yaml": ("CROSS-", "SUPPLY-"),
}


def _load_yaml(filename: str) -> dict:
    path = RULES_DIR / filename
    assert path.exists(), f"Rule file missing: {path}"
    with open(path) as f:
        return yaml.safe_load(f)


@pytest.fixture(params=["ios_rules.yaml", "android_rules.yaml", "web_rules.yaml", "common_rules.yaml"])
def rule_file(request):
    return request.param


class TestRuleCatalogStructure:
    """Ensure every rule file has valid structure."""

    def test_has_platform_key(self, rule_file):
        data = _load_yaml(rule_file)
        assert "platform" in data

    def test_has_categories(self, rule_file):
        data = _load_yaml(rule_file)
        assert "categories" in data
        assert len(data["categories"]) > 0

    def test_each_category_has_rules(self, rule_file):
        data = _load_yaml(rule_file)
        for cat in data["categories"]:
            assert "name" in cat
            assert "rules" in cat
            assert len(cat["rules"]) > 0

    def test_rule_fields(self, rule_file):
        data = _load_yaml(rule_file)
        for cat in data["categories"]:
            for rule in cat["rules"]:
                assert "id" in rule, f"Missing id in {cat['name']}"
                assert "summary" in rule, f"Missing summary in {rule.get('id', '?')}"
                assert "severity" in rule, f"Missing severity in {rule['id']}"
                assert "fix" in rule, f"Missing fix in {rule['id']}"
                assert "reference" in rule, f"Missing reference in {rule['id']}"

    def test_rule_severity_values(self, rule_file):
        data = _load_yaml(rule_file)
        for cat in data["categories"]:
            for rule in cat["rules"]:
                assert rule["severity"] in VALID_SEVERITIES, (
                    f"Invalid severity '{rule['severity']}' in {rule['id']}"
                )

    def test_rule_id_prefix(self, rule_file):
        data = _load_yaml(rule_file)
        expected_prefix = RULE_ID_PATTERN_MAP[rule_file]
        for cat in data["categories"]:
            for rule in cat["rules"]:
                if isinstance(expected_prefix, tuple):
                    assert any(rule["id"].startswith(p) for p in expected_prefix), (
                        f"Rule {rule['id']} does not start with any of {expected_prefix}"
                    )
                else:
                    assert rule["id"].startswith(expected_prefix), (
                        f"Rule {rule['id']} does not start with {expected_prefix}"
                    )

    def test_no_duplicate_rule_ids(self, rule_file):
        data = _load_yaml(rule_file)
        ids = []
        for cat in data["categories"]:
            for rule in cat["rules"]:
                ids.append(rule["id"])
        assert len(ids) == len(set(ids)), f"Duplicate rule IDs found in {rule_file}"


class TestRuleCounts:
    """Sanity check that we have the expected number of rules."""

    def test_ios_rule_count(self):
        data = _load_yaml("ios_rules.yaml")
        total = sum(len(cat["rules"]) for cat in data["categories"])
        assert total >= 20  # 20 iOS rules total

    def test_android_rule_count(self):
        data = _load_yaml("android_rules.yaml")
        total = sum(len(cat["rules"]) for cat in data["categories"])
        assert total >= 18  # 18 Android rules total

    def test_web_rule_count(self):
        data = _load_yaml("web_rules.yaml")
        total = sum(len(cat["rules"]) for cat in data["categories"])
        assert total >= 27  # 27 Web rules total

    def test_common_rule_count(self):
        data = _load_yaml("common_rules.yaml")
        total = sum(len(cat["rules"]) for cat in data["categories"])
        assert total >= 8  # 8 cross-platform/supply chain rules total


class TestRuleLoader:
    """Tests for the YAML rule loader module."""

    def test_load_ios_rules(self):
        from oncecheck.rules.loader import load_rules
        rules = load_rules("ios")
        assert len(rules) >= 20

    def test_load_android_rules(self):
        from oncecheck.rules.loader import load_rules
        rules = load_rules("android")
        assert len(rules) >= 18

    def test_load_web_rules(self):
        from oncecheck.rules.loader import load_rules
        rules = load_rules("web")
        assert len(rules) >= 27

    def test_load_common_rules(self):
        from oncecheck.rules.loader import load_rules
        rules = load_rules("common")
        assert len(rules) >= 8

    def test_get_rule_ids(self):
        from oncecheck.rules.loader import get_rule_ids
        ids = get_rule_ids("ios")
        assert "IOS-PRIV-001" in ids
        assert "IOS-SEC-003" in ids
        assert "IOS-PRIV-007" in ids

    def test_get_rule_by_id(self):
        from oncecheck.rules.loader import get_rule_by_id
        rule = get_rule_by_id("ios", "IOS-PRIV-001")
        assert rule is not None
        assert rule["severity"] == "FAIL"

    def test_get_rule_by_id_not_found(self):
        from oncecheck.rules.loader import get_rule_by_id
        rule = get_rule_by_id("ios", "NONEXISTENT")
        assert rule is None

    def test_validate_rules_no_errors(self):
        from oncecheck.rules.loader import validate_rules
        for platform in ("ios", "android", "web", "common"):
            errors = validate_rules(platform)
            assert errors == [], f"Validation errors for {platform}: {errors}"

    def test_load_unknown_platform(self):
        from oncecheck.rules.loader import load_rules
        rules = load_rules("unknown_platform")
        assert rules == []
