"""Tests for the Rule Validator module."""

from __future__ import annotations

import json

import pytest

from mcpaudit.rule_validator import (
    RuleValidator,
    ValidationIssue,
    ValidationResult,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _rule(
    rule_id: str = "SEC-001",
    severity: str = "HIGH",
    title: str = "Test rule",
    description: str = "This is a test security rule",
    cwe: str = "",
    owasp: str = "",
    recommendation: str = "Fix it",
    **kwargs,
) -> dict:
    result = {
        "rule_id": rule_id,
        "severity": severity,
        "title": title,
        "description": description,
        "recommendation": recommendation,
    }
    if cwe:
        result["cwe"] = cwe
    if owasp:
        result["owasp"] = owasp
    result.update(kwargs)
    return result


# ===========================================================================
# ValidationIssue
# ===========================================================================

class TestValidationIssue:
    def test_to_dict(self):
        i = ValidationIssue("SEC-001", "ERROR", "bad", "field")
        d = i.to_dict()
        assert d["rule_id"] == "SEC-001"
        assert d["level"] == "ERROR"


# ===========================================================================
# ValidationResult
# ===========================================================================

class TestValidationResult:
    def test_empty(self):
        r = ValidationResult()
        assert r.is_valid
        assert r.error_count == 0
        assert r.warning_count == 0

    def test_with_errors(self):
        r = ValidationResult(issues=[
            ValidationIssue("R1", "ERROR", "bad"),
            ValidationIssue("R2", "WARNING", "warn"),
        ])
        assert not r.is_valid
        assert r.error_count == 1
        assert r.warning_count == 1

    def test_to_json(self):
        r = ValidationResult(rules_checked=5)
        j = r.to_json()
        data = json.loads(j)
        assert data["rules_checked"] == 5
        assert data["is_valid"] is True


# ===========================================================================
# RuleValidator - single rule
# ===========================================================================

class TestRuleValidatorSingleRule:
    def test_valid_rule(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule())
        errors = [i for i in issues if i.level == "ERROR"]
        assert len(errors) == 0

    def test_missing_rule_id(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(rule_id=""))
        errors = [i for i in issues if i.level == "ERROR"]
        assert len(errors) >= 1

    def test_missing_severity(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(severity=""))
        errors = [i for i in issues if i.level == "ERROR"]
        assert len(errors) >= 1

    def test_missing_title(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(title=""))
        errors = [i for i in issues if i.level == "ERROR"]
        assert len(errors) >= 1

    def test_invalid_rule_id_format(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(rule_id="bad-format"))
        errors = [i for i in issues if i.level == "ERROR" and "format" in i.message.lower()]
        assert len(errors) >= 1

    def test_valid_rule_id_formats(self):
        v = RuleValidator()
        for rid in ["SEC-001", "CMD-123", "CUSTOM-0001", "A-001"]:
            issues = v.validate_rule(_rule(rule_id=rid))
            format_errors = [i for i in issues if i.level == "ERROR" and "format" in i.message.lower()]
            assert len(format_errors) == 0, f"Rule ID '{rid}' should be valid"

    def test_invalid_severity(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(severity="EXTREME"))
        errors = [i for i in issues if i.level == "ERROR" and "severity" in i.message.lower()]
        assert len(errors) >= 1

    def test_all_valid_severities(self):
        v = RuleValidator()
        for sev in ("CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"):
            issues = v.validate_rule(_rule(severity=sev))
            sev_errors = [i for i in issues if i.level == "ERROR" and "severity" in i.field]
            assert len(sev_errors) == 0

    def test_invalid_cwe(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(cwe="CVE-2024-1234"))
        warns = [i for i in issues if i.level == "WARNING" and "CWE" in i.message]
        assert len(warns) >= 1

    def test_valid_cwe(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(cwe="CWE-78"))
        cwe_warns = [i for i in issues if i.level == "WARNING" and "CWE" in i.message]
        assert len(cwe_warns) == 0

    def test_invalid_owasp(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(owasp="A99"))
        warns = [i for i in issues if i.level == "WARNING" and "OWASP" in i.message]
        assert len(warns) >= 1

    def test_valid_owasp(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(owasp="A02"))
        owasp_warns = [i for i in issues if i.level == "WARNING" and "OWASP" in i.message]
        assert len(owasp_warns) == 0

    def test_short_description_warning(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(description="short"))
        warns = [i for i in issues if i.level == "WARNING" and "short" in i.message.lower()]
        assert len(warns) >= 1

    def test_no_recommendation_info(self):
        v = RuleValidator()
        issues = v.validate_rule(_rule(recommendation=""))
        infos = [i for i in issues if i.level == "INFO" and "recommendation" in i.message.lower()]
        assert len(infos) >= 1


# ===========================================================================
# RuleValidator - multiple rules
# ===========================================================================

class TestRuleValidatorMultipleRules:
    def test_valid_set(self):
        v = RuleValidator()
        result = v.validate_rules([
            _rule("SEC-001"),
            _rule("SEC-002"),
            _rule("SEC-003"),
        ])
        assert result.is_valid
        assert result.rules_checked == 3

    def test_duplicate_ids(self):
        v = RuleValidator()
        result = v.validate_rules([
            _rule("SEC-001"),
            _rule("SEC-001"),
        ])
        dup_errors = [i for i in result.issues if "Duplicate" in i.message]
        assert len(dup_errors) >= 1

    def test_severity_consistency_warning(self):
        v = RuleValidator()
        result = v.validate_rules([
            _rule("SEC-001", severity="CRITICAL"),
            _rule("SEC-002", severity="INFO"),
        ])
        consistency_warns = [i for i in result.issues if "CRITICAL" in i.message and "INFO" in i.message]
        assert len(consistency_warns) >= 1

    def test_empty_list(self):
        v = RuleValidator()
        result = v.validate_rules([])
        assert result.is_valid
        assert result.rules_checked == 0


# ===========================================================================
# RuleValidator - report
# ===========================================================================

class TestRuleValidatorReport:
    def test_report_all_valid(self):
        v = RuleValidator()
        result = v.validate_rules([_rule()])
        report = v.markdown_report(result)
        assert "# Rule Validation Report" in report
        assert "Valid**: Yes" in report

    def test_report_with_errors(self):
        v = RuleValidator()
        result = v.validate_rules([_rule(severity="INVALID")])
        report = v.markdown_report(result)
        assert "Errors" in report
        assert "Valid**: No" in report
