"""Tests for Secret Scanner — dedicated secret detection engine."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

import pytest

from mcpaudit.secret_scanner import (
    SecretFinding,
    SecretPattern,
    calculate_entropy,
    format_secrets_json,
    format_secrets_text,
    get_secret_patterns,
    scan_secrets,
    scan_secrets_directory,
    scan_secrets_file,
    _mask_value,
    _is_likely_false_positive,
)


# ---------------------------------------------------------------------------
# calculate_entropy
# ---------------------------------------------------------------------------

class TestCalculateEntropy:
    """Tests for Shannon entropy calculation."""

    def test_empty_string(self):
        assert calculate_entropy("") == 0.0

    def test_single_char(self):
        assert calculate_entropy("a") == 0.0

    def test_all_same_chars(self):
        assert calculate_entropy("aaaa") == 0.0

    def test_two_distinct_chars_50_50(self):
        # "ab" = 1.0 bits/char
        result = calculate_entropy("ab")
        assert abs(result - 1.0) < 0.01

    def test_high_entropy_random(self):
        # Random-looking string should have high entropy
        token = "aK3bZ9xQ7mN2wP5jR8cT4vL6yH1uF0"
        result = calculate_entropy(token)
        assert result > 3.5

    def test_low_entropy_repeated(self):
        result = calculate_entropy("aaaaabbbbb")
        assert result < 1.5

    def test_moderate_entropy(self):
        result = calculate_entropy("hello world")
        assert 2.0 < result < 4.0

    def test_hex_string_entropy(self):
        result = calculate_entropy("0123456789abcdef0123456789abcdef")
        assert result > 3.0

    def test_base64_string_entropy(self):
        result = calculate_entropy("SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0")
        assert result > 3.5


# ---------------------------------------------------------------------------
# SecretFinding dataclass
# ---------------------------------------------------------------------------

class TestSecretFinding:
    """Tests for SecretFinding dataclass."""

    def test_to_dict(self):
        f = SecretFinding(
            type="api_key",
            value_masked="AKIA***",
            file="server.py",
            line=10,
            entropy=4.5,
            confidence="HIGH",
            pattern_name="AWS Access Key ID",
            severity="CRITICAL",
            description="AWS key detected",
        )
        d = f.to_dict()
        assert d["type"] == "api_key"
        assert d["value_masked"] == "AKIA***"
        assert d["file"] == "server.py"
        assert d["line"] == 10
        assert d["entropy"] == 4.5
        assert d["confidence"] == "HIGH"
        assert d["severity"] == "CRITICAL"

    def test_default_fields(self):
        f = SecretFinding(
            type="token",
            value_masked="ghp_***",
            file="test.py",
            line=1,
            entropy=3.0,
            confidence="MEDIUM",
        )
        assert f.pattern_name == ""
        assert f.severity == "HIGH"
        assert f.description == ""


# ---------------------------------------------------------------------------
# SecretPattern dataclass
# ---------------------------------------------------------------------------

class TestSecretPattern:
    """Tests for SecretPattern dataclass."""

    def test_to_dict(self):
        import re
        p = SecretPattern(
            name="Test Pattern",
            pattern=re.compile(r"test-\d+"),
            severity="HIGH",
            description="Test pattern",
            type="api_key",
        )
        d = p.to_dict()
        assert d["name"] == "Test Pattern"
        assert d["severity"] == "HIGH"
        assert "pattern" in d


# ---------------------------------------------------------------------------
# get_secret_patterns
# ---------------------------------------------------------------------------

class TestGetSecretPatterns:
    """Tests for getting built-in patterns."""

    def test_returns_list(self):
        patterns = get_secret_patterns()
        assert isinstance(patterns, list)

    def test_has_25_plus_patterns(self):
        patterns = get_secret_patterns()
        assert len(patterns) >= 25

    def test_all_have_required_fields(self):
        patterns = get_secret_patterns()
        for p in patterns:
            assert p.name
            assert p.pattern
            assert p.severity in ("CRITICAL", "HIGH", "MEDIUM", "LOW")
            assert p.description
            assert p.type in ("api_key", "token", "password", "certificate", "private_key")

    def test_returns_copies(self):
        p1 = get_secret_patterns()
        p2 = get_secret_patterns()
        assert p1 is not p2


# ---------------------------------------------------------------------------
# _mask_value
# ---------------------------------------------------------------------------

class TestMaskValue:
    """Tests for value masking."""

    def test_long_value(self):
        assert _mask_value("AKIAIOSFODNN7EXAMPLE") == "AKIA***"

    def test_short_value(self):
        assert _mask_value("abc") == "a***"

    def test_exact_4_chars(self):
        assert _mask_value("abcd") == "a***"

    def test_5_chars(self):
        assert _mask_value("abcde") == "abcd***"

    def test_empty_value(self):
        assert _mask_value("") == "***"


# ---------------------------------------------------------------------------
# _is_likely_false_positive
# ---------------------------------------------------------------------------

class TestFalsePositiveDetection:
    """Tests for false positive filtering."""

    def test_short_value(self):
        assert _is_likely_false_positive("abc") is True

    def test_placeholder_values(self):
        assert _is_likely_false_positive("your_api_key") is True
        assert _is_likely_false_positive("changeme") is True
        assert _is_likely_false_positive("placeholder") is True
        assert _is_likely_false_positive("PLACEHOLDER") is True

    def test_all_same_chars(self):
        assert _is_likely_false_positive("xxxxxxxx") is True

    def test_real_looking_secret(self):
        assert _is_likely_false_positive("aK3bZ9xQ7mN2wP5jR8cT4v") is False


# ---------------------------------------------------------------------------
# scan_secrets — Pattern detection
# ---------------------------------------------------------------------------

class TestScanSecrets:
    """Tests for scanning source code for secrets."""

    def test_detects_aws_access_key(self):
        source = 'aws_key = "AKIAIOSFODNN7EXAMPLE1"'
        findings = scan_secrets(source, "test.py")
        assert any(f.pattern_name == "AWS Access Key ID" for f in findings)

    def test_detects_github_token(self):
        source = 'token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz12"'
        findings = scan_secrets(source, "test.py")
        assert any("GitHub" in f.pattern_name for f in findings)

    def test_detects_database_url(self):
        source = 'db_url = "postgres://admin:secretpass123@db.example.com:5432/mydb"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_detects_private_key(self):
        source = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIB..."
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_detects_ec_private_key(self):
        source = "-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBD..."
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_detects_openssh_private_key(self):
        source = "-----BEGIN OPENSSH PRIVATE KEY-----\nABCDEF..."
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_detects_pgp_private_key(self):
        source = "-----BEGIN PGP PRIVATE KEY BLOCK-----\nABCDEF..."
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_detects_jwt_secret(self):
        source = 'JWT_SECRET = "my_super_secret_jwt_key_1234567890"'
        findings = scan_secrets(source, "test.py")
        assert any("JWT" in f.pattern_name for f in findings)

    def test_detects_slack_bot_token(self):
        source = 'slack_token = "xoxb-1234567890-AbCdEfGhIjKlMnOp"'
        findings = scan_secrets(source, "test.py")
        assert any("Slack" in f.pattern_name for f in findings)

    def test_detects_stripe_secret_key(self):
        source = 'stripe_key = "sk_live_1234567890abcdefghijklmno"'
        findings = scan_secrets(source, "test.py")
        assert any("Stripe" in f.pattern_name for f in findings)

    def test_detects_sendgrid_key(self):
        source = 'sg_key = "SG.abcdefghij1234567890AB.abcdefghij1234567890AB"'
        findings = scan_secrets(source, "test.py")
        assert any("SendGrid" in f.pattern_name for f in findings)

    def test_detects_google_api_key(self):
        source = 'google_key = "AIzaSyA1234567890abcdefghijklmnopqrstuv"'
        findings = scan_secrets(source, "test.py")
        assert any("Google" in f.pattern_name for f in findings)

    def test_detects_password_assignment(self):
        source = 'password = "super_secret_password_123"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_detects_generic_api_key(self):
        source = 'API_KEY = "AbCdEf1234567890GhIjKl"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "api_key" for f in findings)

    def test_detects_oauth_client_secret(self):
        source = 'client_secret = "AbCdEfGhIjKlMnOpQrStUvWxYz123456"'
        findings = scan_secrets(source, "test.py")
        assert any("OAuth" in f.pattern_name or "client_secret" in f.pattern_name.lower() or "Google" in f.pattern_name for f in findings)

    def test_no_false_positive_for_placeholder(self):
        source = 'api_key = "your_api_key"'
        findings = scan_secrets(source, "test.py")
        # Should not flag placeholder
        api_findings = [f for f in findings if f.pattern_name == "Generic API Key assignment"]
        assert len(api_findings) == 0

    def test_no_findings_clean_code(self):
        source = '''
def hello():
    print("Hello World")
    return 42
'''
        findings = scan_secrets(source, "test.py")
        assert len(findings) == 0

    def test_multiline_source(self):
        source = '''
config = {
    "database": "postgres://user:pa$$word_complex_123@host:5432/db",
    "debug": True,
}
'''
        findings = scan_secrets(source, "test.py")
        assert len(findings) >= 1

    def test_dedup_same_line(self):
        source = 'key = "AKIAIOSFODNN7EXAMPLE1" key2 = "AKIAIOSFODNN7EXAMPLE1"'
        findings = scan_secrets(source, "test.py")
        # Same pattern on same line should be deduplicated
        aws_findings = [f for f in findings if f.pattern_name == "AWS Access Key ID"]
        assert len(aws_findings) == 1


# ---------------------------------------------------------------------------
# scan_secrets — High entropy detection
# ---------------------------------------------------------------------------

class TestHighEntropyDetection:
    """Tests for high-entropy string detection."""

    def test_high_entropy_string_detected(self):
        # A random-looking base64 string
        source = 'secret = "aK3bZ9xQ7mN2wP5jR8cT4vL6yH1uF0eX"'
        findings = scan_secrets(source, "test.py")
        # Should detect as high entropy or as a pattern match
        assert len(findings) >= 1

    def test_low_entropy_not_flagged(self):
        source = 'name = "aaaaaabbbbbbccccccdd"'
        findings = scan_secrets(source, "test.py")
        # Low entropy string in quotes
        he_findings = [f for f in findings if f.type == "high_entropy"]
        # Should not have high entropy findings for this
        for f in he_findings:
            assert f.entropy > 4.5


# ---------------------------------------------------------------------------
# scan_secrets — .env file support
# ---------------------------------------------------------------------------

class TestEnvFileScanning:
    """Tests for .env file scanning."""

    def test_detects_env_secrets(self):
        source = """
SECRET=my_production_secret_key_12345
TOKEN=abcdefghijklmnop123456789
API_KEY=sk_live_1234567890abcdefghijklmno
"""
        findings = scan_secrets(source, ".env")
        assert len(findings) >= 1

    def test_detects_env_database_url(self):
        source = 'DATABASE_URL=postgres://admin:secret_pass_123@db:5432/myapp'
        findings = scan_secrets(source, ".env")
        assert any(f.type == "password" for f in findings)


# ---------------------------------------------------------------------------
# scan_secrets_file
# ---------------------------------------------------------------------------

class TestScanSecretsFile:
    """Tests for file-based scanning."""

    def test_scan_existing_file(self, tmp_path: Path):
        f = tmp_path / "config.py"
        f.write_text('API_KEY = "sk_live_1234567890abcdefghijklmno"\n')
        findings = scan_secrets_file(str(f))
        assert len(findings) >= 1

    def test_scan_nonexistent_file(self):
        findings = scan_secrets_file("/nonexistent/path/file.py")
        assert findings == []

    def test_scan_clean_file(self, tmp_path: Path):
        f = tmp_path / "clean.py"
        f.write_text('print("Hello")\n')
        findings = scan_secrets_file(str(f))
        assert findings == []

    def test_findings_have_correct_file_path(self, tmp_path: Path):
        f = tmp_path / "secrets.py"
        f.write_text('password = "super_secret_pass_123"\n')
        findings = scan_secrets_file(str(f))
        assert len(findings) >= 1
        assert findings[0].file == str(f)


# ---------------------------------------------------------------------------
# scan_secrets_directory
# ---------------------------------------------------------------------------

class TestScanSecretsDirectory:
    """Tests for directory scanning."""

    def test_scan_directory(self, tmp_path: Path):
        (tmp_path / "a.py").write_text('key = "sk_live_1234567890abcdefghijklmno"\n')
        (tmp_path / "b.js").write_text('const x = "ghp_1234567890abcdefghijklmnopqrstuvwxyz12"\n')
        findings = scan_secrets_directory(str(tmp_path))
        assert len(findings) >= 2

    def test_scan_directory_with_dotenv(self, tmp_path: Path):
        (tmp_path / ".env").write_text('SECRET=my_production_secret_key_12345\n')
        findings = scan_secrets_directory(str(tmp_path))
        assert len(findings) >= 1

    def test_scan_empty_directory(self, tmp_path: Path):
        findings = scan_secrets_directory(str(tmp_path))
        assert findings == []

    def test_scan_file_as_directory(self, tmp_path: Path):
        f = tmp_path / "test.py"
        f.write_text('password = "super_secret_pass_123"\n')
        findings = scan_secrets_directory(str(f))
        assert len(findings) >= 1


# ---------------------------------------------------------------------------
# format_secrets_text
# ---------------------------------------------------------------------------

class TestFormatSecretsText:
    """Tests for text formatting."""

    def test_no_findings(self):
        output = format_secrets_text([])
        assert "No secrets detected" in output

    def test_with_findings(self):
        findings = [
            SecretFinding(
                type="api_key",
                value_masked="AKIA***",
                file="test.py",
                line=5,
                entropy=4.2,
                confidence="HIGH",
                pattern_name="AWS Access Key ID",
                severity="CRITICAL",
                description="AWS key detected",
            ),
        ]
        output = format_secrets_text(findings)
        assert "CRITICAL" in output
        assert "AWS" in output
        assert "test.py" in output
        assert "Secret Scanner" in output

    def test_sorted_by_severity(self):
        findings = [
            SecretFinding(
                type="password", value_masked="pass***", file="a.py",
                line=1, entropy=3.0, confidence="MEDIUM",
                severity="MEDIUM", pattern_name="Password",
            ),
            SecretFinding(
                type="api_key", value_masked="AKIA***", file="b.py",
                line=2, entropy=4.0, confidence="HIGH",
                severity="CRITICAL", pattern_name="AWS Key",
            ),
        ]
        output = format_secrets_text(findings)
        # CRITICAL should appear before MEDIUM
        crit_pos = output.index("CRITICAL")
        med_pos = output.index("MEDIUM")
        assert crit_pos < med_pos


# ---------------------------------------------------------------------------
# format_secrets_json
# ---------------------------------------------------------------------------

class TestFormatSecretsJson:
    """Tests for JSON formatting."""

    def test_valid_json(self):
        findings = [
            SecretFinding(
                type="token", value_masked="ghp_***", file="test.py",
                line=1, entropy=4.0, confidence="HIGH",
                pattern_name="GitHub Token",
            ),
        ]
        output = format_secrets_json(findings)
        data = json.loads(output)
        assert "secret_scanner" in data
        assert data["secret_scanner"]["total_findings"] == 1

    def test_empty_findings_json(self):
        output = format_secrets_json([])
        data = json.loads(output)
        assert data["secret_scanner"]["total_findings"] == 0
        assert data["secret_scanner"]["findings"] == []


# ---------------------------------------------------------------------------
# CLI integration
# ---------------------------------------------------------------------------

class TestSecretsCLI:
    """Tests for CLI subcommand integration."""

    def test_cli_scan_file(self, tmp_path: Path):
        f = tmp_path / "secret.py"
        f.write_text('API_KEY = "sk_live_1234567890abcdefghijklmno"\n')

        from mcpaudit.__main__ import main
        exit_code = main(["secrets", str(f)])
        assert exit_code == 1  # 1 because secrets found

    def test_cli_scan_clean(self, tmp_path: Path):
        f = tmp_path / "clean.py"
        f.write_text('print("hello")\n')

        from mcpaudit.__main__ import main
        exit_code = main(["secrets", str(f)])
        assert exit_code == 0

    def test_cli_json_format(self, tmp_path: Path, capsys):
        f = tmp_path / "secret.py"
        f.write_text('password = "super_secret_pass_123"\n')

        from mcpaudit.__main__ import main
        main(["secrets", str(f), "--format", "json"])
        captured = capsys.readouterr()
        data = json.loads(captured.out)
        assert "secret_scanner" in data

    def test_cli_output_file(self, tmp_path: Path):
        f = tmp_path / "secret.py"
        f.write_text('password = "super_secret_pass_123"\n')
        out = tmp_path / "report.txt"

        from mcpaudit.__main__ import main
        main(["secrets", str(f), "--output", str(out)])
        assert out.exists()
        content = out.read_text()
        assert len(content) > 0

    def test_cli_nonexistent_path(self):
        from mcpaudit.__main__ import main
        exit_code = main(["secrets", "/nonexistent/path"])
        assert exit_code == 1

    def test_cli_min_entropy_filter(self, tmp_path: Path, capsys):
        f = tmp_path / "test.py"
        f.write_text('password = "super_secret_pass_123"\n')

        from mcpaudit.__main__ import main
        main(["secrets", str(f), "--min-entropy", "99.0", "--format", "json"])
        captured = capsys.readouterr()
        data = json.loads(captured.out)
        assert data["secret_scanner"]["total_findings"] == 0


# ---------------------------------------------------------------------------
# Additional pattern detection tests (uncovered patterns)
# ---------------------------------------------------------------------------

class TestScanSecretsAdditionalPatterns:
    """Tests for remaining secret patterns not yet covered."""

    def test_detects_aws_secret_access_key(self):
        source = 'aws_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"'
        findings = scan_secrets(source, "test.py")
        assert any("AWS Secret" in f.pattern_name for f in findings)

    def test_detects_github_gho_token(self):
        source = 'token = "gho_1234567890abcdefghijklmnopqrstuvwxyz12"'
        findings = scan_secrets(source, "test.py")
        assert any("gho" in f.pattern_name for f in findings)

    def test_detects_github_ghs_token(self):
        source = 'token = "ghs_1234567890abcdefghijklmnopqrstuvwxyz12"'
        findings = scan_secrets(source, "test.py")
        assert any("ghs" in f.pattern_name for f in findings)

    def test_detects_github_ghr_token(self):
        source = 'token = "ghr_1234567890abcdefghijklmnopqrstuvwxyz12"'
        findings = scan_secrets(source, "test.py")
        assert any("ghr" in f.pattern_name for f in findings)

    def test_detects_slack_webhook_url(self):
        source = 'webhook = "https://hooks.slack.com/services/T0ABCDEFG/B0ABCDEFG/1234567890abcdef"'
        findings = scan_secrets(source, "test.py")
        assert any("Slack Webhook" in f.pattern_name for f in findings)

    def test_detects_stripe_restricted_key(self):
        source = 'rkey = "rk_live_1234567890abcdefghijklmno"'
        findings = scan_secrets(source, "test.py")
        assert any("Stripe Restricted" in f.pattern_name for f in findings)

    def test_detects_twilio_api_key(self):
        source = 'twilio_key = "SK1234567890abcdef1234567890abcdef"'
        findings = scan_secrets(source, "test.py")
        assert any("Twilio" in f.pattern_name for f in findings)

    def test_detects_mailgun_api_key(self):
        source = 'mg_key = "key-1234567890abcdef1234567890abcdef"'
        findings = scan_secrets(source, "test.py")
        assert any("Mailgun" in f.pattern_name for f in findings)

    def test_detects_heroku_api_key(self):
        source = 'HEROKU_API_KEY = "12345678-1234-1234-1234-123456789012"'
        findings = scan_secrets(source, "test.py")
        assert any("Heroku" in f.pattern_name for f in findings)

    def test_detects_azure_storage_key(self):
        source = 'conn = "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH=="'
        findings = scan_secrets(source, "test.py")
        assert any("Azure" in f.pattern_name for f in findings)

    def test_detects_generic_private_key(self):
        source = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqh..."
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_detects_env_variable_secret(self):
        source = 'SECRET=my_production_secret_key_12345'
        findings = scan_secrets(source, ".env")
        assert len(findings) >= 1

    def test_detects_mongodb_url(self):
        source = 'mongo_url = "mongodb://admin:pa55w0rd_complex@mongo.example.com:27017/mydb"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_detects_mysql_url(self):
        source = 'db = "mysql://root:mysecretpass12@localhost:3306/mydb"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_detects_redis_url(self):
        source = 'redis_url = "redis://default:redispass_12345@redis.example.com:6379"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_detects_mongodb_srv_url(self):
        source = 'mongo = "mongodb+srv://admin:pa55w0rd123@cluster0.example.com/mydb"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)


# ---------------------------------------------------------------------------
# Edge cases and false positive tests
# ---------------------------------------------------------------------------

class TestScanSecretsEdgeCases:
    """Edge cases for secret detection."""

    def test_comment_with_secret_still_detected(self):
        source = '# api_key = "sk_live_1234567890abcdefghijklmno"'
        findings = scan_secrets(source, "test.py")
        # Should still detect — comments can contain leaked secrets
        assert len(findings) >= 1

    def test_multiline_private_key(self):
        source = """key = \"\"\"
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0Z3VS5JJc...
-----END RSA PRIVATE KEY-----
\"\"\"
"""
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "private_key" for f in findings)

    def test_password_in_yaml_format(self):
        source = 'password: "complex_password_with_special_chars_123"'
        findings = scan_secrets(source, "config.yaml")
        assert any(f.type == "password" for f in findings)

    def test_password_with_colon_separator(self):
        source = 'pwd: "my_secret_production_pwd_12345"'
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_no_detection_for_test_token_placeholder(self):
        source = 'api_key = "your_api_key_here"'
        findings = scan_secrets(source, "test.py")
        api_findings = [f for f in findings if f.pattern_name == "Generic API Key assignment"]
        assert len(api_findings) == 0

    def test_no_detection_for_example_password(self):
        source = 'password = "example_password"'
        findings = scan_secrets(source, "test.py")
        pwd_findings = [f for f in findings if f.pattern_name == "Password Assignment"]
        assert len(pwd_findings) == 0

    def test_empty_source(self):
        findings = scan_secrets("", "test.py")
        assert findings == []

    def test_binary_looking_content(self):
        source = "\x00\x01\x02\x03"
        findings = scan_secrets(source, "test.bin")
        assert isinstance(findings, list)

    def test_very_long_line(self):
        source = "a" * 10000
        findings = scan_secrets(source, "test.py")
        assert isinstance(findings, list)

    def test_unicode_content(self):
        source = 'password = "contrase\u00f1a_secreta_importante_123"'
        findings = scan_secrets(source, "test.py")
        assert isinstance(findings, list)

    def test_multiple_secrets_same_file(self):
        source = """
api_key = "sk_live_1234567890abcdefghijklmno"
password = "super_secret_pass_12345_prod"
db_url = "postgres://admin:dbpass_complex_123@host:5432/db"
"""
        findings = scan_secrets(source, "test.py")
        assert len(findings) >= 3

    def test_secret_finding_entropy_is_positive(self):
        source = 'api_key = "sk_live_1234567890abcdefghijklmno"'
        findings = scan_secrets(source, "test.py")
        assert len(findings) >= 1
        for f in findings:
            assert f.entropy >= 0.0

    def test_findings_have_correct_line_numbers(self):
        source = """line1
line2
password = "my_secret_production_password"
line4
"""
        findings = scan_secrets(source, "test.py")
        pwd_findings = [f for f in findings if f.type == "password"]
        assert len(pwd_findings) >= 1
        assert pwd_findings[0].line == 3

    def test_secret_in_single_quotes(self):
        source = "password = 'single_quoted_secret_password_123'"
        findings = scan_secrets(source, "test.py")
        assert any(f.type == "password" for f in findings)

    def test_scan_directory_skips_unsupported_extensions(self, tmp_path):
        (tmp_path / "image.png").write_bytes(b"\x89PNG\r\n")
        (tmp_path / "secret.py").write_text('password = "super_secret_pass_123"\n')
        findings = scan_secrets_directory(str(tmp_path))
        # Should find in .py but not try to parse .png
        assert all(f.file.endswith(".py") for f in findings)

    def test_scan_directory_recursive(self, tmp_path):
        sub = tmp_path / "subdir"
        sub.mkdir()
        (sub / "config.py").write_text('api_key = "sk_live_1234567890abcdefghijklmno"\n')
        findings = scan_secrets_directory(str(tmp_path))
        assert len(findings) >= 1


# ---------------------------------------------------------------------------
# Entropy edge cases
# ---------------------------------------------------------------------------

class TestEntropyEdgeCases:
    """Additional entropy calculation tests."""

    def test_all_distinct_chars(self):
        # Maximum entropy for length-N string with all unique chars
        result = calculate_entropy("abcdefghij")
        import math
        expected = math.log2(10)  # ~3.32
        assert abs(result - expected) < 0.01

    def test_entropy_symmetry(self):
        # Entropy should be the same regardless of char order
        e1 = calculate_entropy("aabbccdd")
        e2 = calculate_entropy("abcdabcd")
        assert abs(e1 - e2) < 0.01

    def test_entropy_increases_with_diversity(self):
        e1 = calculate_entropy("aaaa")
        e2 = calculate_entropy("aabb")
        e3 = calculate_entropy("abcd")
        assert e1 < e2 < e3

    def test_long_random_string_high_entropy(self):
        # Simulate a high-entropy token
        token = "aK3bZ9xQ7mN2wP5jR8cT4vL6yH1uF0eXgDiOlBsWp"
        result = calculate_entropy(token)
        assert result > 4.0


# ---------------------------------------------------------------------------
# Format edge cases
# ---------------------------------------------------------------------------

class TestFormatEdgeCases:
    """Edge cases for formatting functions."""

    def test_format_text_multiple_findings(self):
        findings = [
            SecretFinding(
                type="api_key", value_masked="sk_l***", file="a.py",
                line=1, entropy=4.0, confidence="HIGH",
                severity="CRITICAL", pattern_name="Stripe Key",
                description="Stripe key detected",
            ),
            SecretFinding(
                type="token", value_masked="ghp_***", file="b.py",
                line=5, entropy=3.8, confidence="HIGH",
                severity="HIGH", pattern_name="GitHub Token",
                description="GitHub token detected",
            ),
            SecretFinding(
                type="high_entropy", value_masked="aK3b***", file="c.py",
                line=10, entropy=4.8, confidence="MEDIUM",
                severity="MEDIUM", pattern_name="High Entropy String",
                description="High entropy string",
            ),
        ]
        output = format_secrets_text(findings)
        assert "3" in output  # total count
        assert "Stripe" in output
        assert "GitHub" in output
        assert "High Entropy" in output

    def test_format_json_preserves_all_fields(self):
        findings = [
            SecretFinding(
                type="api_key", value_masked="AKIA***", file="test.py",
                line=10, entropy=4.5, confidence="HIGH",
                pattern_name="AWS Key", severity="CRITICAL",
                description="AWS key detected",
            ),
        ]
        output = format_secrets_json(findings)
        data = json.loads(output)
        finding = data["secret_scanner"]["findings"][0]
        assert finding["type"] == "api_key"
        assert finding["value_masked"] == "AKIA***"
        assert finding["entropy"] == 4.5
        assert finding["severity"] == "CRITICAL"
        assert finding["pattern_name"] == "AWS Key"

    def test_format_text_low_severity_icon(self):
        findings = [
            SecretFinding(
                type="api_key", value_masked="key_***", file="test.py",
                line=1, entropy=3.0, confidence="LOW",
                severity="LOW", pattern_name="Generic Key",
            ),
        ]
        output = format_secrets_text(findings)
        assert "[.]" in output
