"""
core/patterns.py — VaultHound v1.0.0
Secret detection pattern library.

Each pattern: (name, severity, regex, validator_fn_or_None)
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Callable, Optional


@dataclass
class Pattern:
    name:      str
    severity:  str
    regex:     re.Pattern
    validator: Optional[Callable[[str], bool]] = None
    context:   str = ""  # short description shown in report


# ── Validators ────────────────────────────────────────────────────────────────

def _min_entropy(s: str, min_bits: float = 3.0) -> bool:
    """Shannon entropy check — filters out low-entropy false positives."""
    import math
    if not s:
        return False
    freq = {c: s.count(c) / len(s) for c in set(s)}
    entropy = -sum(p * math.log2(p) for p in freq.values())
    return entropy >= min_bits


def _not_placeholder(s: str) -> bool:
    """Filter out common placeholder values."""
    placeholders = {
        "your_key_here", "your-key-here", "xxxxxxxxxx",
        "changeme", "change_me", "replace_me", "your_secret",
        "secretkey", "secret_key", "mysecret", "example",
        "placeholder", "todo", "fixme", "null", "none",
        "true", "false", "1234567890", "abcdefghij",
        "test", "testing", "demo", "sample",
    }
    return s.lower() not in placeholders and len(set(s)) > 4


def _valid_secret(s: str) -> bool:
    return _not_placeholder(s) and _min_entropy(s, 3.0)


def _valid_high_entropy(s: str) -> bool:
    return _not_placeholder(s) and _min_entropy(s, 3.5)


# ── Pattern definitions ───────────────────────────────────────────────────────

PATTERNS: list[Pattern] = [

    # ── Cloud providers ──────────────────────────────────────────────────────
    Pattern(
        name      = "AWS Access Key ID",
        severity  = "critical",
        regex     = re.compile(r"(?<![A-Z0-9])((AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16,20})(?![A-Z0-9])"),
        validator = _not_placeholder,
        context   = "AWS access key — grants API access to AWS services",
    ),
    Pattern(
        name      = "AWS Secret Access Key",
        severity  = "critical",
        regex     = re.compile(r"(?i)aws[_\-\s]*secret[_\-\s]*(?:access[_\-\s]*)?key[\"'\s]*[:=][\"'\s]*([A-Za-z0-9/+]{40})"),
        validator = _valid_high_entropy,
        context   = "AWS secret — used with access key to sign API requests",
    ),
    Pattern(
        name      = "AWS Session Token",
        severity  = "critical",
        regex     = re.compile(r"(?i)aws[_\-\s]*session[_\-\s]*token[\"'\s]*[:=][\"'\s]*([A-Za-z0-9/+=]{100,})"),
        validator = _valid_secret,
        context   = "Temporary AWS credentials",
    ),
    Pattern(
        name      = "GCP API Key",
        severity  = "critical",
        regex     = re.compile(r"AIza[0-9A-Za-z\-_]{30,}"),
        validator = _not_placeholder,
        context   = "Google Cloud Platform API key",
    ),
    Pattern(
        name      = "GCP Service Account",
        severity  = "critical",
        regex     = re.compile(r'"type"\s*:\s*"service_account"'),
        context   = "GCP service account JSON — contains private key",
    ),
    Pattern(
        name      = "Azure Storage Key",
        severity  = "critical",
        regex     = re.compile(r"(?i)(?:azure|storage)[_\-\s]*(?:account[_\-\s]*)?key[\"'\s]*[:=][\"'\s]*([A-Za-z0-9+/]{86}==)"),
        validator = _valid_high_entropy,
        context   = "Azure storage account key",
    ),
    Pattern(
        name      = "Azure Connection String",
        severity  = "critical",
        regex     = re.compile(r"DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]+"),
        context   = "Azure storage connection string with credentials",
    ),

    # ── Payment ──────────────────────────────────────────────────────────────
    Pattern(
        name      = "Stripe Secret Key",
        severity  = "critical",
        regex     = re.compile(r"sk_live_[A-Za-z0-9_]{24,}"),
        validator = _not_placeholder,
        context   = "Stripe live secret key — full payment API access",
    ),
    Pattern(
        name      = "Stripe Restricted Key",
        severity  = "high",
        regex     = re.compile(r"rk_live_[A-Za-z0-9_]{24,}"),
        validator = _not_placeholder,
        context   = "Stripe restricted key",
    ),
    Pattern(
        name      = "Stripe Publishable Key",
        severity  = "low",
        regex     = re.compile(r"pk_live_[A-Za-z0-9_]{24,}"),
        validator = _not_placeholder,
        context   = "Stripe publishable key — confirms Stripe is in use",
    ),
    Pattern(
        name      = "PayPal / Braintree Token",
        severity  = "critical",
        regex     = re.compile(r"access_token\$production\$[A-Za-z0-9]{16}\$[A-Za-z0-9]{32}"),
        context   = "Braintree production access token",
    ),
    Pattern(
        name      = "Square Access Token",
        severity  = "critical",
        regex     = re.compile(r"sq0atp-[A-Za-z0-9\-_]{22}"),
        context   = "Square payment access token",
    ),

    # ── Communication APIs ────────────────────────────────────────────────────
    Pattern(
        name      = "Twilio Account SID",
        severity  = "high",
        regex     = re.compile(r"AC[a-zA-Z0-9]{32}"),
        validator = _not_placeholder,
        context   = "Twilio account identifier",
    ),
    Pattern(
        name      = "Twilio Auth Token",
        severity  = "critical",
        regex     = re.compile(r"(?i)twilio[_\-\s]*auth[_\-\s]*token[\"'\s]*[:=][\"'\s]*([a-f0-9]{32})"),
        validator = _valid_high_entropy,
        context   = "Twilio auth token — full SMS/call API access",
    ),
    Pattern(
        name      = "SendGrid API Key",
        severity  = "critical",
        regex     = re.compile(r"SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{40,}"),
        context   = "SendGrid email API key",
    ),
    Pattern(
        name      = "Mailgun API Key",
        severity  = "high",
        regex     = re.compile(r"key-[A-Za-z0-9]{32}"),
        validator = _not_placeholder,
        context   = "Mailgun API key",
    ),
    Pattern(
        name      = "Slack Bot Token",
        severity  = "high",
        regex     = re.compile(r"xoxb-[0-9]{11,13}-[0-9]{11,13}-[A-Za-z0-9]{24}"),
        context   = "Slack bot token — channel read/write access",
    ),
    Pattern(
        name      = "Slack User Token",
        severity  = "critical",
        regex     = re.compile(r"xoxp-[0-9]{11,13}-[0-9]{11,13}-[0-9]{11,13}-[A-Za-z0-9]{32}"),
        context   = "Slack user token — full account access",
    ),
    Pattern(
        name      = "Slack Webhook",
        severity  = "high",
        regex     = re.compile(r"https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+"),
        context   = "Slack incoming webhook URL — can post messages",
    ),
    Pattern(
        name      = "Discord Bot Token",
        severity  = "high",
        regex     = re.compile(r"(?i)discord[_\-\s]*(?:bot[_\-\s]*)?token[\"'\s]*[:=][\"'\s]*([A-Za-z0-9\-_]{59,})"),
        validator = _valid_high_entropy,
        context   = "Discord bot token",
    ),

    # ── Source control & CI/CD ────────────────────────────────────────────────
    Pattern(
        name      = "GitHub Personal Access Token",
        severity  = "critical",
        regex     = re.compile(r"ghp_[A-Za-z0-9_]{30,}"),
        context   = "GitHub PAT — repo read/write access",
    ),
    Pattern(
        name      = "GitHub OAuth Token",
        severity  = "critical",
        regex     = re.compile(r"gho_[A-Za-z0-9_]{30,}"),
        context   = "GitHub OAuth token",
    ),
    Pattern(
        name      = "GitHub App Token",
        severity  = "critical",
        regex     = re.compile(r"(ghu|ghs)_[A-Za-z0-9_]{30,}"),
        context   = "GitHub App installation or user token",
    ),
    Pattern(
        name      = "GitLab Personal Token",
        severity  = "critical",
        regex     = re.compile(r"glpat-[A-Za-z0-9\-_]{20}"),
        context   = "GitLab personal access token",
    ),
    Pattern(
        name      = "NPM Token",
        severity  = "high",
        regex     = re.compile(r"npm_[A-Za-z0-9]{36}"),
        context   = "NPM publish/access token",
    ),
    Pattern(
        name      = "PyPI Token",
        severity  = "high",
        regex     = re.compile(r"pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,}"),
        context   = "PyPI package publish token",
    ),

    # ── AI / LLM APIs ─────────────────────────────────────────────────────────
    Pattern(
        name      = "OpenAI API Key",
        severity  = "critical",
        regex     = re.compile(r"sk-[A-Za-z0-9_]{48}"),
        validator = _not_placeholder,
        context   = "OpenAI API key — paid usage access",
    ),
    Pattern(
        name      = "Anthropic API Key",
        severity  = "critical",
        regex     = re.compile(r"sk-ant-[A-Za-z0-9\-_]{60,}"),
        context   = "Anthropic / Claude API key",
    ),
    Pattern(
        name      = "Groq API Key",
        severity  = "critical",
        regex     = re.compile(r"gsk_[A-Za-z0-9]{52}"),
        context   = "Groq LLM API key",
    ),
    Pattern(
        name      = "HuggingFace Token",
        severity  = "high",
        regex     = re.compile(r"hf_[A-Za-z0-9]{34}"),
        context   = "HuggingFace API token",
    ),

    # ── Database credentials ──────────────────────────────────────────────────
    Pattern(
        name      = "Database Connection String",
        severity  = "critical",
        regex     = re.compile(
            r"(?i)(?:mysql|postgresql|postgres|mongodb|redis|mssql|oracle|sqlite)"
            r"://[A-Za-z0-9_\-]+:[A-Za-z0-9!@#$%^&*()_+\-=]{4,}@[A-Za-z0-9.\-]+"
        ),
        validator = _valid_secret,
        context   = "Database URI with embedded credentials",
    ),
    Pattern(
        name      = "MongoDB Atlas Connection",
        severity  = "critical",
        regex     = re.compile(r"mongodb\+srv://[A-Za-z0-9_\-]+:[A-Za-z0-9!@#$%^&*_+\-=]{4,}@"),
        context   = "MongoDB Atlas connection string with password",
    ),

    # ── Private keys ──────────────────────────────────────────────────────────
    Pattern(
        name      = "RSA Private Key",
        severity  = "critical",
        regex     = re.compile(r"-----BEGIN RSA PRIVATE KEY-----"),
        context   = "RSA private key — used for SSH, TLS, JWT signing",
    ),
    Pattern(
        name      = "EC Private Key",
        severity  = "critical",
        regex     = re.compile(r"-----BEGIN EC PRIVATE KEY-----"),
        context   = "Elliptic curve private key",
    ),
    Pattern(
        name      = "OpenSSH Private Key",
        severity  = "critical",
        regex     = re.compile(r"-----BEGIN OPENSSH PRIVATE KEY-----"),
        context   = "OpenSSH private key",
    ),
    Pattern(
        name      = "PGP Private Key",
        severity  = "critical",
        regex     = re.compile(r"-----BEGIN PGP PRIVATE KEY BLOCK-----"),
        context   = "PGP private key block",
    ),

    # ── JWT ───────────────────────────────────────────────────────────────────
    Pattern(
        name      = "JWT Token",
        severity  = "medium",
        regex     = re.compile(r"eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}"),
        validator = _valid_secret,
        context   = "JSON Web Token — may contain user data or session info",
    ),

    # ── Generic secrets ───────────────────────────────────────────────────────
    Pattern(
        name      = "Generic API Key",
        severity  = "medium",
        regex     = re.compile(
            r"(?i)(?:api[_\-\s]*key|apikey|api_secret|app_key|app_secret)"
            r"[\"'\s]*[:=][\"'\s]*([A-Za-z0-9\-_]{20,})"
        ),
        validator = _valid_high_entropy,
        context   = "Generic API key assignment",
    ),
    Pattern(
        name      = "Generic Secret",
        severity  = "medium",
        regex     = re.compile(
            r"(?i)(?:secret[_\-]?key|secret_token|secret_value|client_secret)"
            r"[\"'\s]*[:=][\"'\s]*([A-Za-z0-9\-_+/=]{16,})"
        ),
        validator = _valid_high_entropy,
        context   = "Generic secret key/token assignment",
    ),
    Pattern(
        name      = "Generic Password",
        severity  = "high",
        regex     = re.compile(
            r"(?i)(?:password|passwd|pwd|pass)[\"'\s]*[:=][\"'\s]*([^\s\"'\\]{8,})"
        ),
        validator = _valid_high_entropy,
        context   = "Hardcoded password",
    ),
    Pattern(
        name      = "Generic Token",
        severity  = "medium",
        regex     = re.compile(
            r"(?i)(?:access_token|auth_token|bearer_token|refresh_token)"
            r"[\"'\s]*[:=][\"'\s]*([A-Za-z0-9\-_+/=]{20,})"
        ),
        validator = _valid_high_entropy,
        context   = "Generic token assignment",
    ),

    # ── Infrastructure ────────────────────────────────────────────────────────
    Pattern(
        name      = "Private IPv4 in Config",
        severity  = "info",
        regex     = re.compile(
            r"(?i)(?:host|server|endpoint|db_host|database_host)[\"'\s]*[:=][\"'\s]*"
            r"(10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2[0-9]|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+)"
        ),
        context   = "Internal IP address in configuration — reveals network topology",
    ),
    Pattern(
        name      = "Hardcoded Internal URL",
        severity  = "low",
        regex     = re.compile(
            r"(?i)(?:url|endpoint|host|base_url)[\"'\s]*[:=][\"'\s]*"
            r"(https?://(?:localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+)[^\s\"']*)"
        ),
        context   = "Hardcoded internal/localhost URL",
    ),
]


# ── Public API ────────────────────────────────────────────────────────────────

def get_patterns(custom_file: str | None = None) -> list[Pattern]:
    """Return all patterns, optionally extended with custom YAML patterns."""
    patterns = list(PATTERNS)
    if custom_file:
        patterns.extend(_load_custom_patterns(custom_file))
    return patterns


def _load_custom_patterns(path: str) -> list[Pattern]:
    """Load custom patterns from a YAML file."""
    try:
        import yaml
        with open(path) as f:
            data = yaml.safe_load(f)
        result = []
        for p in data.get("patterns", []):
            result.append(Pattern(
                name     = p["name"],
                severity = p.get("severity", "medium"),
                regex    = re.compile(p["regex"]),
                context  = p.get("context", ""),
            ))
        return result
    except Exception as e:
        from utils.logger import log
        log.warning(f"Failed to load custom patterns from {path}: {e}")
        return []
