"""Secret redaction for AutoDebug captures."""

from __future__ import annotations

import re
from typing import Any, Dict, List, Set

# Environment variables that should always be redacted
REDACT_ENV_VARS: Set[str] = {
    # AWS
    "AWS_ACCESS_KEY_ID",
    "AWS_SECRET_ACCESS_KEY",
    "AWS_SESSION_TOKEN",
    # Database
    "DATABASE_URL",
    "DB_PASSWORD",
    "POSTGRES_PASSWORD",
    "MYSQL_PASSWORD",
    "REDIS_PASSWORD",
    "MONGO_PASSWORD",
    # API keys
    "API_KEY",
    "API_SECRET",
    "SECRET_KEY",
    "PRIVATE_KEY",
    # Auth
    "AUTH_TOKEN",
    "ACCESS_TOKEN",
    "REFRESH_TOKEN",
    "JWT_SECRET",
    "SESSION_SECRET",
    # Cloud providers
    "AZURE_CLIENT_SECRET",
    "GCP_CREDENTIALS",
    "GOOGLE_APPLICATION_CREDENTIALS",
    # Services
    "STRIPE_SECRET_KEY",
    "SENDGRID_API_KEY",
    "TWILIO_AUTH_TOKEN",
    "SLACK_TOKEN",
    "GITHUB_TOKEN",
    "NPM_TOKEN",
    "PYPI_TOKEN",
    # Generic
    "PASSWORD",
    "SECRET",
    "CREDENTIAL",
    "CREDENTIALS",
}

# Patterns that indicate sensitive values in variable names
SENSITIVE_NAME_PATTERNS: List[re.Pattern] = [
    re.compile(r"(?i)(api[_-]?key|secret|token|password|credential)", re.IGNORECASE),
    re.compile(r"(?i)(aws[_-]?(access|secret))", re.IGNORECASE),
    re.compile(r"(?i)(database[_-]?url|connection[_-]?string)", re.IGNORECASE),
    re.compile(r"(?i)(private[_-]?key|auth[_-]?token)", re.IGNORECASE),
]

# Patterns that indicate sensitive values in content
SENSITIVE_VALUE_PATTERNS: List[re.Pattern] = [
    # AWS keys
    re.compile(r"AKIA[0-9A-Z]{16}"),  # AWS Access Key
    re.compile(r"(?i)aws[_-]?secret[_-]?access[_-]?key"),
    # Bearer tokens
    re.compile(r"Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+"),  # JWT
    re.compile(r"Bearer\s+[A-Za-z0-9\-_]{20,}"),
    # API keys (generic patterns)
    re.compile(r"(?i)api[_-]?key[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9\-_]{20,}"),
    re.compile(r"sk_live_[A-Za-z0-9]{24,}"),  # Stripe
    re.compile(r"sk_test_[A-Za-z0-9]{24,}"),  # Stripe test
    # Database URLs with passwords
    re.compile(r"(?i)(postgres|mysql|mongodb)://[^:]+:[^@]+@"),
    # GitHub tokens (PAT, OAuth, etc.)
    re.compile(r"gh[pousr]_[A-Za-z0-9]{36,}"),
    # Additional connection string protocols
    re.compile(r"(?i)(redis|amqp|mssql|sqlite)://[^:]+:[^@]+@"),
]

REDACTED_VALUE = "[REDACTED]"

# Header names that must always be redacted (compared case-insensitively)
_SENSITIVE_HEADER_NAMES: Set[str] = {
    "authorization",
    "x-api-key",
    "cookie",
    "set-cookie",
    "x-amz-security-token",
    "proxy-authorization",
}


def should_redact_env_var(name: str) -> bool:
    """Check if an environment variable name should be redacted."""
    # Check exact matches
    if name.upper() in REDACT_ENV_VARS:
        return True

    # Check patterns
    for pattern in SENSITIVE_NAME_PATTERNS:
        if pattern.search(name):
            return True

    return False


def redact_value(value: str) -> str:
    """Redact sensitive patterns from a value."""
    redacted = value

    for pattern in SENSITIVE_VALUE_PATTERNS:
        redacted = pattern.sub(REDACTED_VALUE, redacted)

    return redacted


def redact_env_fingerprint(fingerprint: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from environment fingerprint."""
    result = fingerprint.copy()

    if "env_vars_read" in result:
        env_vars = result["env_vars_read"].copy()
        for name in list(env_vars.keys()):
            if should_redact_env_var(name):
                env_vars[name] = REDACTED_VALUE
        result["env_vars_read"] = env_vars

    return result


def _redact_headers_dict(headers: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive header values from a headers dict (case-insensitive)."""
    redacted = headers.copy()
    for key in list(redacted.keys()):
        if key.lower() in _SENSITIVE_HEADER_NAMES:
            redacted[key] = REDACTED_VALUE
    return redacted


def redact_http_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from HTTP record.

    Handles multiple header key conventions used by different parts of
    the probe:
      - "request_headers" (legacy / direct HTTP capture)
      - "headers_canon"   (canonicalised headers from the probe)
      - "headers_raw"     (raw headers from the probe)
    Also handles body fields:
      - "request_body", "response_body" (legacy)
      - "response_body_bytes" (probe convention)
    """
    result = record.copy()

    # Redact all known header dict fields (case-insensitive key matching)
    for header_field in ("request_headers", "headers_canon", "headers_raw"):
        if header_field in result and isinstance(result[header_field], dict):
            result[header_field] = _redact_headers_dict(result[header_field])

    # Redact sensitive values in URL
    if "url" in result:
        result["url"] = redact_value(result["url"])
    if "url_canonical" in result:
        result["url_canonical"] = redact_value(result["url_canonical"])

    # Redact sensitive values in request body
    if "request_body" in result and isinstance(result["request_body"], str):
        result["request_body"] = redact_value(result["request_body"])

    # Redact sensitive values in response body (both key conventions)
    if "response_body" in result and isinstance(result["response_body"], str):
        result["response_body"] = redact_value(result["response_body"])
    if "response_body_bytes" in result and isinstance(result["response_body_bytes"], str):
        result["response_body_bytes"] = redact_value(result["response_body_bytes"])

    return result


def redact_sql_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from SQL record."""
    result = record.copy()

    # Redact connection strings
    if "connection_string" in result:
        result["connection_string"] = REDACTED_VALUE

    # Redact password parameters
    if "params" in result and isinstance(result["params"], dict):
        params = result["params"].copy()
        for key in list(params.keys()):
            if should_redact_env_var(key):
                params[key] = REDACTED_VALUE
        result["params"] = params

    return result


def _redact_dict_sensitive_fields(d: Dict[str, Any]) -> Dict[str, Any]:
    """Recursively redact values whose keys match sensitive name patterns.

    Walks a nested dict/list structure and replaces values at keys that look
    like they hold secrets (password, token, secret, credential, auth, etc.).
    """
    if not isinstance(d, dict):
        return d

    result = {}
    for key, value in d.items():
        if isinstance(key, str) and any(p.search(key) for p in SENSITIVE_NAME_PATTERNS):
            result[key] = REDACTED_VALUE
        elif isinstance(value, dict):
            result[key] = _redact_dict_sensitive_fields(value)
        elif isinstance(value, list):
            result[key] = [
                _redact_dict_sensitive_fields(item) if isinstance(item, dict) else item
                for item in value
            ]
        else:
            result[key] = value

    return result


def _redact_string_field(record: Dict[str, Any], field: str) -> None:
    """Redact sensitive value patterns within a string field, in place."""
    if field in record and isinstance(record[field], str):
        record[field] = redact_value(record[field])


def redact_redis_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from a Redis record.

    - Redacts AUTH command arguments (passwords).
    - Redacts redis:// connection strings in key patterns.
    - Scans result values for sensitive patterns.
    """
    result = record.copy()

    # Redact AUTH command args -- the args list contains the password
    command = (result.get("command") or "").upper()
    if command == "AUTH" and "args" in result:
        if isinstance(result["args"], (list, tuple)):
            result["args"] = [REDACTED_VALUE for _ in result["args"]]
        else:
            result["args"] = REDACTED_VALUE

    # Redact connection strings
    if "connection_string" in result:
        result["connection_string"] = REDACTED_VALUE

    # Scan key for embedded connection strings (e.g. redis://user:pass@host)
    _redact_string_field(result, "key")

    # Scan result value for sensitive patterns
    _redact_string_field(result, "result")
    _redact_string_field(result, "response")

    return result


def redact_mongodb_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from a MongoDB record.

    - Redacts connection strings.
    - Scans query filters for sensitive field names.
    - Scans results for sensitive patterns.
    """
    result = record.copy()

    # Redact connection strings
    if "connection_string" in result:
        result["connection_string"] = REDACTED_VALUE

    # Scan query/filter dicts for sensitive field names
    for field in ("query", "filter", "update", "document", "pipeline"):
        if field in result and isinstance(result[field], dict):
            result[field] = _redact_dict_sensitive_fields(result[field])
        elif field in result and isinstance(result[field], list):
            result[field] = [
                _redact_dict_sensitive_fields(item) if isinstance(item, dict) else item
                for item in result[field]
            ]

    # Scan string result fields for sensitive patterns
    _redact_string_field(result, "result")
    _redact_string_field(result, "response")

    return result


def redact_grpc_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from a gRPC record.

    - Scans protobuf field dicts for sensitive field names.
    - Redacts authorization metadata.
    """
    result = record.copy()

    # Scan request/response protobuf dicts for sensitive field names
    for field in ("request", "response", "request_body", "response_body"):
        if field in result and isinstance(result[field], dict):
            result[field] = _redact_dict_sensitive_fields(result[field])

    # Redact sensitive metadata (authorization, tokens, etc.)
    for meta_field in ("metadata", "request_metadata", "response_metadata"):
        if meta_field in result and isinstance(result[meta_field], dict):
            result[meta_field] = _redact_headers_dict(result[meta_field])
        elif meta_field in result and isinstance(result[meta_field], list):
            # Metadata can be a list of (key, value) tuples
            redacted_meta = []
            for item in result[meta_field]:
                if isinstance(item, (list, tuple)) and len(item) == 2:
                    key, value = item
                    if isinstance(key, str) and key.lower() in _SENSITIVE_HEADER_NAMES:
                        redacted_meta.append((key, REDACTED_VALUE))
                    else:
                        redacted_meta.append(item)
                else:
                    redacted_meta.append(item)
            result[meta_field] = redacted_meta

    return result


def redact_aws_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from an AWS record.

    - Redacts response body sensitive patterns.
    - Redacts header credentials (reuses HTTP header redaction).
    - Redacts IAM/STS tokens in responses.
    """
    result = record.copy()

    # Redact headers (Authorization, x-amz-security-token, etc.)
    for header_field in ("request_headers", "headers", "response_headers"):
        if header_field in result and isinstance(result[header_field], dict):
            result[header_field] = _redact_headers_dict(result[header_field])

    # Redact sensitive values in request/response body strings
    for body_field in ("request_body", "response_body", "body"):
        _redact_string_field(result, body_field)

    # Redact IAM/STS tokens in response dicts
    for resp_field in ("response", "result"):
        if resp_field in result and isinstance(result[resp_field], dict):
            resp = result[resp_field]
            # Walk known STS/IAM credential keys
            for token_key in (
                "SecretAccessKey", "SessionToken", "AccessKeyId",
                "Credentials", "Token", "Password",
            ):
                if token_key in resp:
                    if isinstance(resp[token_key], str):
                        resp[token_key] = REDACTED_VALUE
                    elif isinstance(resp[token_key], dict):
                        # e.g. Credentials dict from STS AssumeRole
                        for sub_key in list(resp[token_key].keys()):
                            if sub_key in ("SecretAccessKey", "SessionToken", "AccessKeyId"):
                                resp[token_key][sub_key] = REDACTED_VALUE
            result[resp_field] = resp

    return result


def redact_record(record: Dict[str, Any]) -> Dict[str, Any]:
    """Redact sensitive values from any record based on type."""
    record_type = record.get("type", "")

    if record_type == "HTTP":
        return redact_http_record(record)
    elif record_type == "SQL":
        return redact_sql_record(record)
    elif record_type == "REDIS":
        return redact_redis_record(record)
    elif record_type == "MONGODB":
        return redact_mongodb_record(record)
    elif record_type == "GRPC":
        return redact_grpc_record(record)
    elif record_type == "AWS":
        return redact_aws_record(record)
    else:
        return record


def get_redaction_summary(records: List[Dict[str, Any]]) -> Dict[str, Any]:
    """Generate summary of what was/will be redacted."""
    summary = {
        "env_vars_redacted": [],
        "headers_redacted": [],
        "patterns_found": 0,
    }

    for record in records:
        record_type = record.get("type", "")

        if record_type == "HTTP":
            # Check all header dict fields for sensitive keys
            for header_field in ("request_headers", "headers_canon", "headers_raw"):
                if header_field in record and isinstance(record[header_field], dict):
                    for key in record[header_field]:
                        if key.lower() in _SENSITIVE_HEADER_NAMES and key not in summary["headers_redacted"]:
                            summary["headers_redacted"].append(key)

            # Check for patterns in URL/body
            for field in ["url", "request_body", "response_body", "response_body_bytes"]:
                if field in record and isinstance(record[field], str):
                    for pattern in SENSITIVE_VALUE_PATTERNS:
                        if pattern.search(record[field]):
                            summary["patterns_found"] += 1

    return summary
