"""Regression tests — verify each provider's example is detected through Scanner."""

import pytest
from argus_nano.patterns import PatternRegistry
from argus_nano.scanner import Scanner

# Patterns whose format regex is identical to (or a subset of) a benign
# pattern.  These are intentionally suppressed at the regex level and
# rely on semantic detection to catch real instances in config files.
_BENIGN_SUPPRESSED_IDS = {"heroku_api_key"}


@pytest.fixture(scope="module")
def scanner():
    return Scanner(patterns_only=True)


@pytest.fixture(scope="module")
def registry():
    return PatternRegistry()


# ------------------------------------------------------------------
# Positive: every provider's first should_detect example is caught
# ------------------------------------------------------------------


def test_all_providers_detected(scanner, registry):
    """For each provider pattern, the first should_detect value must produce
    a ScanResult with the correct pattern_id when checked."""
    failures = []
    for pat in registry.providers:
        if pat["id"] in _BENIGN_SUPPRESSED_IDS:
            continue
        test_cases = pat.get("test_cases", {})
        examples = test_cases.get("should_detect", [])
        if not examples:
            continue
        example = examples[0]
        results = scanner.check(example)
        found_ids = {r.pattern_id for r in results}
        if pat["id"] not in found_ids:
            failures.append(f"{pat['id']}: example {example!r} not detected (got ids: {found_ids})")
    assert failures == [], "Provider detection regressions:\n" + "\n".join(failures)


def test_detected_results_have_correct_fields(scanner, registry):
    """Verify that results carry correct provider and severity."""
    for pat in registry.providers:
        test_cases = pat.get("test_cases", {})
        examples = test_cases.get("should_detect", [])
        if not examples:
            continue
        example = examples[0]
        results = scanner.check(example)
        matching = [r for r in results if r.pattern_id == pat["id"]]
        for r in matching:
            assert r.provider == pat["provider"], f"{pat['id']}: expected provider {pat['provider']}, got {r.provider}"
            assert r.severity == pat["severity"], f"{pat['id']}: expected severity {pat['severity']}, got {r.severity}"


# ------------------------------------------------------------------
# Benign suppression regression: NO should_detect example may be
# suppressed by is_benign().  This catches patterns whose test
# values accidentally contain UUID, hash, or other benign substrings.
# ------------------------------------------------------------------


def test_should_detect_not_suppressed_by_benign(registry):
    """For EVERY provider pattern, ALL should_detect examples must NOT
    be suppressed by is_benign() when the pattern_id is provided."""
    failures = []
    for pat in registry.providers:
        if pat["id"] in _BENIGN_SUPPRESSED_IDS:
            continue
        test_cases = pat.get("test_cases", {})
        examples = test_cases.get("should_detect", [])
        for example in examples:
            if registry.is_benign(example, pat["id"]):
                failures.append(f"{pat['id']}: should_detect value {example!r} suppressed by is_benign()")
    assert failures == [], "Benign filter suppression regressions:\n" + "\n".join(failures)


def test_should_detect_not_suppressed_by_benign_without_override(registry):
    """Even without benign_override, the length-ratio check must not
    suppress any should_detect example (covers patterns that don't
    have benign_override set)."""
    failures = []
    for pat in registry.providers:
        # Skip patterns that explicitly need benign_override — their
        # token format inherently overlaps with benign patterns (e.g.
        # Infisical tokens contain 64 hex chars that match SHA-256).
        if pat.get("benign_override"):
            continue
        # Skip patterns intentionally suppressed by benign (e.g. heroku
        # UUIDs are handled by semantic detection instead).
        if pat["id"] in _BENIGN_SUPPRESSED_IDS:
            continue
        test_cases = pat.get("test_cases", {})
        examples = test_cases.get("should_detect", [])
        for example in examples:
            # Call is_benign WITHOUT pattern_id to test the length-ratio
            # check alone (benign_override is not consulted).
            if registry.is_benign(example):
                failures.append(
                    f"{pat['id']}: should_detect value {example!r} suppressed by is_benign() even without pattern_id"
                )
    assert failures == [], "Length-ratio regressions (no override):\n" + "\n".join(failures)


# ------------------------------------------------------------------
# Negative: known false positives are NOT detected
# ------------------------------------------------------------------


_FALSE_POSITIVES = [
    ("UUID v4", "550e8400-e29b-41d4-a716-446655440000"),
    ("SHA-256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
    ("Placeholder", "YOUR_API_KEY"),
    ("Placeholder", "CHANGEME"),
    ("Placeholder", "example_key"),
    ("Localhost postgres", "postgres://user:pass@localhost:5432/devdb"),
    ("Localhost redis", "redis://localhost:6379/0"),
    ("bcrypt", "$2a$12$WApznUPhDubN0oeveSXHp.Rk0rHIiOaezFnBGRPKnoVaGnmIhEyam"),
    ("Zero UUID", "00000000-0000-0000-0000-000000000000"),
    ("Docker digest", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
    ("Base64 test", "dGVzdA=="),
    ("Hex color", "#ff5733"),
    ("X.509 BasicConstraints", "Basic Constraints"),
]


@pytest.mark.parametrize("name,value", _FALSE_POSITIVES, ids=[f[0] for f in _FALSE_POSITIVES])
def test_false_positive_not_detected(scanner, registry, name, value):
    """Known benign values must be filtered out by the benign registry."""
    assert registry.is_benign(value), f"{name}: {value!r} should be benign"
