"""Tests for semantic secret detection."""

import pytest
from argus_nano.scanner import Scanner
from argus_nano.semantic import (
    SemanticScanner,
    classify_key,
    extract_assignments,
    has_value_evidence,
    is_placeholder,
)

# ------------------------------------------------------------------
# Key classification tests
# ------------------------------------------------------------------


def test_high_signal_password():
    assert classify_key("POSTGRES_PASSWORD") == (True, False)


def test_high_signal_secret():
    assert classify_key("APP_SECRET") == (True, False)


def test_high_signal_exact_password():
    assert classify_key("password") == (True, False)


def test_high_signal_exact_api_key():
    assert classify_key("api_key") == (True, False)


def test_high_signal_prefix_db_password():
    assert classify_key("db_password") == (True, False)


def test_high_signal_prefix_redis_password():
    assert classify_key("redis_password") == (True, False)


def test_high_signal_suffix_creds():
    assert classify_key("aws_creds") == (True, False)


def test_ambiguous_key():
    """cache_key is in safe_keys, so it should be (False, False)."""
    assert classify_key("cache_key") == (False, False)


def test_ambiguous_token():
    assert classify_key("session_token") == (True, True)


def test_ambiguous_exact_token():
    assert classify_key("token") == (True, True)


def test_safe_primary_key():
    assert classify_key("primary_key") == (False, False)


def test_safe_foreign_key():
    assert classify_key("foreign_key") == (False, False)


def test_safe_password_policy():
    assert classify_key("password_policy") == (False, False)


def test_safe_token_expiry():
    assert classify_key("token_expiry") == (False, False)


def test_safe_public_key():
    assert classify_key("public_key") == (False, False)


def test_safe_hash():
    assert classify_key("hash") == (False, False)


def test_case_insensitive():
    assert classify_key("DB_PASSWORD") == (True, False)
    assert classify_key("db_password") == (True, False)
    assert classify_key("Db_Password") == (True, False)


# ------------------------------------------------------------------
# Placeholder detection tests
# ------------------------------------------------------------------


def test_placeholder_changeme():
    assert is_placeholder("changeme") is True


def test_placeholder_env_var():
    assert is_placeholder("${DB_PASS}") is True


def test_placeholder_dollar_var():
    assert is_placeholder("$DB_PASS") is True


def test_not_placeholder_dollar_word():
    """$uperSecret starts with $ but is not an env var reference."""
    assert is_placeholder("$uperSecret") is False


def test_placeholder_template():
    assert is_placeholder("<your-api-key>") is True


def test_placeholder_mustache():
    assert is_placeholder("{{API_KEY}}") is True


def test_placeholder_curly_simple():
    assert is_placeholder("{api_key}") is True


def test_not_placeholder_real():
    assert is_placeholder("Company2024!") is False


def test_placeholder_empty():
    assert is_placeholder("") is True


def test_placeholder_short():
    assert is_placeholder("ab") is True


def test_placeholder_none_value():
    assert is_placeholder("none") is True


def test_placeholder_null_value():
    assert is_placeholder("null") is True


def test_placeholder_boolean_false():
    assert is_placeholder("false") is True


def test_placeholder_boolean_true():
    assert is_placeholder("true") is True


def test_placeholder_boolean_yes():
    assert is_placeholder("yes") is True


def test_placeholder_boolean_no():
    # "no" is < 3 chars so also caught by length check
    assert is_placeholder("no") is True


def test_placeholder_todo():
    assert is_placeholder("TODO") is True


def test_placeholder_prefix_your():
    assert is_placeholder("your_api_key_here") is True


def test_placeholder_prefix_example():
    assert is_placeholder("example_password") is True


def test_placeholder_windows_env():
    assert is_placeholder("%DB_PASSWORD%") is True


def test_placeholder_python_format():
    assert is_placeholder("%(db_password)s") is True


def test_placeholder_all_same_char():
    assert is_placeholder("xxxxxxxxxx") is True


# ------------------------------------------------------------------
# Value evidence tests
# ------------------------------------------------------------------


def test_evidence_strong_password():
    assert has_value_evidence("xK9mP2q!") is True


def test_evidence_short():
    assert has_value_evidence("abc") is False


def test_evidence_single_class_lower():
    assert has_value_evidence("alllowercase") is False


def test_evidence_single_class_digits():
    assert has_value_evidence("123456789") is False


def test_evidence_url_no_creds():
    assert has_value_evidence("https://api.example.com") is False


def test_evidence_url_with_creds():
    assert has_value_evidence("postgres://user:pass@host/db") is True


def test_evidence_mixed_case_digits():
    assert has_value_evidence("Admin1234") is True


def test_evidence_natural_language():
    assert has_value_evidence("this is a long sentence here") is False


# ------------------------------------------------------------------
# File format parser tests
# ------------------------------------------------------------------


def test_extract_env():
    lines = ["PORT=3000", "DB_PASSWORD=hunter2!", "DEBUG=true"]
    results = extract_assignments(lines, "config/.env")
    keys = {r[0] for r in results}
    assert "DB_PASSWORD" in keys
    assert "PORT" in keys


def test_extract_env_export():
    lines = ['export SECRET_KEY="abc123xyz"']
    results = extract_assignments(lines, "deploy.sh")
    assert len(results) == 1
    assert results[0][0] == "SECRET_KEY"
    assert results[0][1] == "abc123xyz"


def test_extract_yaml():
    lines = [
        "app:",
        "  host: localhost",
        '  password: "Company2024!"',
        "  port: 5432",
    ]
    results = extract_assignments(lines, "config.yml")
    keys = {r[0] for r in results}
    assert "password" in keys


def test_extract_yaml_docker_compose_list():
    lines = [
        "services:",
        "  web:",
        "    environment:",
        "      - POSTGRES_PASSWORD=secret123!",
        "      - DEBUG=true",
    ]
    results = extract_assignments(lines, "docker-compose.yml")
    keys = {r[0] for r in results}
    assert "POSTGRES_PASSWORD" in keys


def test_extract_json():
    lines = [
        "{",
        '  "host": "localhost",',
        '  "db_password": "hunter2!abc",',
        '  "port": "5432"',
        "}",
    ]
    results = extract_assignments(lines, "config.json")
    keys = {r[0] for r in results}
    assert "db_password" in keys


def test_extract_toml():
    lines = [
        "[database]",
        'password = "Company2024!"',
        "port = 5432",
    ]
    results = extract_assignments(lines, "config.toml")
    keys = {r[0] for r in results}
    assert "password" in keys


def test_extract_python_assign():
    lines = ['DB_PASSWORD = "hunter2!"']
    results = extract_assignments(lines, "settings.py")
    assert len(results) == 1
    assert results[0][0] == "DB_PASSWORD"


def test_extract_python_dict():
    lines = [
        "config = {",
        '    "db_password": "hunter2!",',
        '    "host": "localhost",',
        "}",
    ]
    results = extract_assignments(lines, "settings.py")
    keys = {r[0] for r in results}
    assert "db_password" in keys


def test_extract_terraform():
    lines = [
        'variable "db_password" {',
        '  default = "Company2024!"',
        "  type    = string",
        "}",
    ]
    results = extract_assignments(lines, "variables.tf")
    # Should use the variable name, not "default"
    assert any(r[0] == "db_password" and r[1] == "Company2024!" for r in results)


def test_extract_terraform_variable_block():
    """Terraform variable blocks should use the variable name as key."""
    lines = [
        "# Infrastructure variables",
        "",
        'variable "region" {',
        '  default = "us-west-2"',
        "}",
        "",
        'variable "encryption_key" {',
        '  default = "130f212340a190ca483057d11f3e3819edc8d820e8f6c434"',
        "  type    = string",
        "}",
    ]
    results = extract_assignments(lines, "vars.tf")
    keys = {r[0]: r[1] for r in results}
    assert "encryption_key" in keys
    assert keys["encryption_key"] == "130f212340a190ca483057d11f3e3819edc8d820e8f6c434"
    assert "region" in keys


def test_extract_terraform_locals():
    """Terraform locals block should keep the local name as key."""
    lines = [
        "locals {",
        '  api_key = "sk_live_abc123def456"',
        '  region  = "us-east-1"',
        "}",
    ]
    results = extract_assignments(lines, "main.tf")
    keys = {r[0]: r[1] for r in results}
    assert "api_key" in keys
    assert keys["api_key"] == "sk_live_abc123def456"


def test_extract_terraform_simple_assign():
    """Simple TF assignments (not inside variable blocks) keep their key."""
    lines = [
        'password = "hunter2secret!"',
    ]
    results = extract_assignments(lines, "config.tf")
    assert any(r[0] == "password" and r[1] == "hunter2secret!" for r in results)


def test_extract_dockerfile():
    lines = ["FROM python:3.11", 'ENV DB_PASSWORD="hunter2!"']
    results = extract_assignments(lines, "Dockerfile")
    keys = {r[0] for r in results}
    assert "DB_PASSWORD" in keys


def test_extract_max_candidates():
    """Safety valve: cap at MAX_CANDIDATES_PER_FILE."""
    lines = [f"KEY_{i}=value_{i}" for i in range(300)]
    results = extract_assignments(lines, "huge.env")
    assert len(results) == 200


def test_extract_parses_commented_lines():
    """Commented-out secrets are still secrets in source control."""
    lines = ["# DB_PASSWORD=secret", "PORT=3000"]
    results = extract_assignments(lines, ".env")
    assert len(results) == 2
    keys = [r[0] for r in results]
    assert "DB_PASSWORD" in keys
    assert "PORT" in keys


def test_extract_commented_yaml_keys():
    """Commented-out YAML key-value pairs should still be extracted."""
    lines = [
        "# password: SuperSecret99!",
        "# db_password: hunter2",
        "port: 5432",
    ]
    results = extract_assignments(lines, "config.yml")
    assert len(results) == 3
    keys = [r[0] for r in results]
    assert "password" in keys
    assert "db_password" in keys
    assert "port" in keys
    # Values should be correctly extracted
    vals = {r[0]: r[1] for r in results}
    assert vals["password"] == "SuperSecret99!"
    assert vals["db_password"] == "hunter2"


def test_extract_empty_comment_skipped():
    """Lines that are just '#' or '# ' should still be skipped."""
    lines = ["#", "# ", "PORT=3000"]
    results = extract_assignments(lines, ".env")
    assert len(results) == 1
    assert results[0][0] == "PORT"


# ------------------------------------------------------------------
# Full semantic scan tests
# ------------------------------------------------------------------


@pytest.fixture
def semantic_scanner():
    return SemanticScanner()


def test_detect_password_env(semantic_scanner):
    lines = ["PORT=3000", "POSTGRES_PASSWORD=Company2024!", "DEBUG=true"]
    results = semantic_scanner.scan_file_content(lines, "config/.env")
    assert len(results) >= 1
    assert results[0].key_name == "POSTGRES_PASSWORD"


def test_detect_password_yaml(semantic_scanner):
    lines = [
        "app:",
        '  password: "Company2024!"',
        "  port: 5432",
    ]
    results = semantic_scanner.scan_file_content(lines, "config.yml")
    assert len(results) >= 1
    assert results[0].key_name == "password"


def test_detect_secret_json(semantic_scanner):
    lines = [
        "{",
        '  "api_secret": "xK9mP2qRaBcDeF123",',
        "}",
    ]
    results = semantic_scanner.scan_file_content(lines, "config.json")
    assert len(results) >= 1
    assert results[0].key_name == "api_secret"


def test_detect_docker_compose_list(semantic_scanner):
    lines = [
        "services:",
        "  web:",
        "    environment:",
        "      - POSTGRES_PASSWORD=Company2024!",
    ]
    results = semantic_scanner.scan_file_content(lines, "docker-compose.yml")
    assert len(results) >= 1


def test_detect_connection_string(semantic_scanner):
    lines = ["DATABASE_URL=postgres://user:Comp2024!@host:5432/db"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) >= 1
    assert results[0].key_name == "DATABASE_URL"


def test_detect_python_dict(semantic_scanner):
    lines = [
        "config = {",
        '    "db_password": "scout_bi!123",',
        "}",
    ]
    results = semantic_scanner.scan_file_content(lines, "settings.py")
    assert len(results) >= 1


def test_skip_placeholder(semantic_scanner):
    lines = ["POSTGRES_PASSWORD=changeme"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_skip_env_var_ref(semantic_scanner):
    lines = ["API_KEY=${API_KEY}"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_skip_dollar_super(semantic_scanner):
    """$uperSecret is NOT a placeholder — should be detected."""
    lines = ["APP_SECRET=$uperSecret123!"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) >= 1


def test_skip_template(semantic_scanner):
    lines = ["SECRET=<your-secret-here>"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_skip_empty(semantic_scanner):
    lines = ['PASSWORD=""']
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_skip_safe_key(semantic_scanner):
    lines = ["primary_key=abc123"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_skip_safe_config(semantic_scanner):
    lines = ["password_min_length=8"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_ambiguous_key_weak_value(semantic_scanner):
    """Ambiguous key + weak value → no evidence → not detected."""
    lines = ["session_token=abc"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) == 0


def test_ambiguous_key_strong_value(semantic_scanner):
    """Ambiguous key + strong value → evidence → detected."""
    lines = ["session_token=xK9mP2q!abc123"]
    results = semantic_scanner.scan_file_content(lines, ".env")
    assert len(results) >= 1


# ------------------------------------------------------------------
# Scanner integration tests
# ------------------------------------------------------------------


def test_disabled_with_flag(tmp_path):
    """With semantic=False, semantic findings should not appear."""
    env_file = tmp_path / ".env"
    env_file.write_text("POSTGRES_PASSWORD=Company2024!\n", encoding="utf-8")
    scanner = Scanner(patterns_only=True, semantic=False)
    results = scanner.scan_file(env_file)
    # POSTGRES_PASSWORD=Company2024! has no format pattern, so no results
    semantic_results = [r for r in results if r.detection_type == "semantic"]
    assert len(semantic_results) == 0


def test_enabled_with_flag(tmp_path):
    """With --semantic, semantic findings should appear."""
    env_file = tmp_path / ".env"
    env_file.write_text("POSTGRES_PASSWORD=Company2024!\n", encoding="utf-8")
    scanner = Scanner(patterns_only=True, semantic=True)
    results = scanner.scan_file(env_file)
    semantic_results = [r for r in results if r.detection_type == "semantic"]
    assert len(semantic_results) >= 1
    assert semantic_results[0].key_name == "POSTGRES_PASSWORD"


def test_no_double_count(tmp_path):
    """If a value is caught by both format and semantic, only one result."""
    env_file = tmp_path / ".env"
    # AKIA... is caught by format regex AND API_KEY is semantic
    env_file.write_text(
        "API_KEY=AKIAIOSFODNN7EXAMPLE\n",
        encoding="utf-8",
    )
    scanner = Scanner(patterns_only=True, semantic=True)
    results = scanner.scan_file(env_file)
    # Dedup should prevent the same (line, value) from appearing twice
    line_values = [(r.line_number, r.value_masked) for r in results]
    assert len(line_values) == len(set(line_values))


def test_scan_result_has_detection_type(tmp_path):
    """ScanResult should include detection_type field."""
    env_file = tmp_path / ".env"
    env_file.write_text("POSTGRES_PASSWORD=Company2024!\n", encoding="utf-8")
    scanner = Scanner(patterns_only=True, semantic=True)
    results = scanner.scan_file(env_file)
    for r in results:
        assert r.detection_type in ("format", "semantic")


def test_scan_result_has_key_name(tmp_path):
    """Semantic ScanResult should include key_name field."""
    env_file = tmp_path / ".env"
    env_file.write_text("DB_PASSWORD=Company2024!\n", encoding="utf-8")
    scanner = Scanner(patterns_only=True, semantic=True)
    results = scanner.scan_file(env_file)
    semantic_results = [r for r in results if r.detection_type == "semantic"]
    assert len(semantic_results) >= 1
    assert semantic_results[0].key_name == "DB_PASSWORD"
