"""
ID Wispera Credential Detection
Pattern-based detection engine with 30+ credential patterns
"""

import json
import re
from pathlib import Path
from typing import Optional

from id_wispera.types import CredentialType, DetectionResult, RiskLevel


# Load patterns from shared JSON
def _load_patterns() -> list[dict]:
    """Load detection patterns from shared JSON file"""
    # Try to load from shared patterns file
    patterns_path = Path(__file__).parent.parent.parent.parent / "shared" / "patterns" / "patterns.json"

    if patterns_path.exists():
        with open(patterns_path) as f:
            data = json.load(f)
            return data.get("patterns", [])

    # Fallback: embedded patterns for standalone distribution
    return _EMBEDDED_PATTERNS


# Embedded patterns (subset for standalone distribution)
_EMBEDDED_PATTERNS = [
    {
        "id": "openai-api-key",
        "name": "OpenAI API Key",
        "regex": r"sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}",
        "confidence": 0.99,
        "credentialType": "api-key",
        "platform": "openai",
        "riskLevel": "critical",
    },
    {
        "id": "openai-project-key",
        "name": "OpenAI Project Key",
        "regex": r"sk-proj-[a-zA-Z0-9_-]{20,}",
        "confidence": 0.99,
        "credentialType": "api-key",
        "platform": "openai",
        "riskLevel": "critical",
    },
    {
        "id": "anthropic-api-key",
        "name": "Anthropic API Key",
        "regex": r"sk-ant-[a-zA-Z0-9_-]{90,}",
        "confidence": 0.99,
        "credentialType": "api-key",
        "platform": "anthropic",
        "riskLevel": "critical",
    },
    {
        "id": "aws-access-key-id",
        "name": "AWS Access Key ID",
        "regex": r"AKIA[0-9A-Z]{16}",
        "confidence": 0.99,
        "credentialType": "api-key",
        "platform": "aws",
        "riskLevel": "critical",
    },
    {
        "id": "github-pat",
        "name": "GitHub Personal Access Token",
        "regex": r"ghp_[a-zA-Z0-9]{36}",
        "confidence": 0.99,
        "credentialType": "api-key",
        "platform": "github",
        "riskLevel": "high",
    },
    {
        "id": "jwt-token",
        "name": "JWT Token",
        "regex": r"eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*",
        "confidence": 0.95,
        "credentialType": "jwt",
        "platform": "custom",
        "riskLevel": "high",
    },
    {
        "id": "private-key",
        "name": "Private Key",
        "regex": r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
        "confidence": 0.99,
        "credentialType": "private-key",
        "platform": "custom",
        "riskLevel": "critical",
    },
]

# Compiled patterns cache
_compiled_patterns: Optional[list[tuple[dict, re.Pattern]]] = None


def _get_compiled_patterns() -> list[tuple[dict, re.Pattern]]:
    """Get compiled regex patterns (cached)"""
    global _compiled_patterns

    if _compiled_patterns is None:
        patterns = _load_patterns()
        _compiled_patterns = []

        for pattern in patterns:
            try:
                flags = 0
                if "i" in pattern.get("flags", ""):
                    flags |= re.IGNORECASE
                if "m" in pattern.get("flags", ""):
                    flags |= re.MULTILINE

                compiled = re.compile(pattern["regex"], flags)
                _compiled_patterns.append((pattern, compiled))
            except re.error:
                # Skip invalid patterns
                continue

    return _compiled_patterns


def detect_credentials(text: str) -> list[DetectionResult]:
    """
    Detect credentials in text using pattern matching.

    Args:
        text: The text to scan for credentials

    Returns:
        List of DetectionResult objects for each credential found
    """
    results: list[DetectionResult] = []
    seen: set[str] = set()

    for pattern_def, compiled in _get_compiled_patterns():
        for match in compiled.finditer(text):
            # Use capture group if present, otherwise full match
            value = match.group(1) if match.lastindex else match.group(0)
            key = f"{value}-{match.start()}"

            if key in seen:
                continue
            seen.add(key)

            # Calculate line and column
            before_match = text[:match.start()]
            lines = before_match.split("\n")
            line = len(lines)
            column = len(lines[-1]) + 1 if lines else 1

            # Get context
            context_start = max(0, match.start() - 20)
            context_end = min(len(text), match.end() + 20)
            context = text[context_start:context_end].replace("\n", "\\n")

            # Map credential type
            cred_type = pattern_def.get("credentialType", "custom")
            try:
                credential_type = CredentialType(cred_type)
            except ValueError:
                credential_type = CredentialType.CUSTOM

            # Map risk level
            risk = pattern_def.get("riskLevel", "medium")
            try:
                risk_level = RiskLevel(risk)
            except ValueError:
                risk_level = RiskLevel.MEDIUM

            results.append(DetectionResult(
                type=credential_type,
                value=value,
                start_index=match.start(),
                end_index=match.end(),
                confidence=pattern_def.get("confidence", 0.5),
                line=line,
                column=column,
                pattern=pattern_def.get("name"),
                context=context,
                risk_level=risk_level,
            ))

    # Sort by position
    results.sort(key=lambda r: r.start_index)

    # Remove overlapping matches (keep higher confidence)
    filtered: list[DetectionResult] = []
    for result in results:
        overlapping = next(
            (r for r in filtered
             if (result.start_index >= r.start_index and result.start_index < r.end_index) or
                (result.end_index > r.start_index and result.end_index <= r.end_index)),
            None
        )

        if overlapping:
            if result.confidence > overlapping.confidence:
                filtered.remove(overlapping)
                filtered.append(result)
        else:
            filtered.append(result)

    return filtered


def classify_credential(value: str) -> CredentialType:
    """
    Classify a known credential value.

    Args:
        value: The credential value to classify

    Returns:
        The most likely CredentialType
    """
    for pattern_def, compiled in _get_compiled_patterns():
        if compiled.search(value) and pattern_def.get("confidence", 0) >= 0.8:
            cred_type = pattern_def.get("credentialType", "custom")
            try:
                return CredentialType(cred_type)
            except ValueError:
                continue

    # Fallback heuristics
    if value.startswith("-----BEGIN") and "PRIVATE KEY" in value:
        return CredentialType.PRIVATE_KEY

    if value.startswith("eyJ") and value.count(".") == 2:
        return CredentialType.JWT

    if "://" in value and "@" in value:
        return CredentialType.CONNECTION_STRING

    return CredentialType.CUSTOM


def might_contain_credentials(text: str) -> bool:
    """
    Quick check if text likely contains credentials.
    Faster than full detection for filtering.

    Args:
        text: The text to check

    Returns:
        True if text might contain credentials
    """
    quick_patterns = [
        r"sk-[a-zA-Z0-9]",
        r"AKIA[0-9A-Z]",
        r"ghp_[a-zA-Z0-9]",
        r"gho_[a-zA-Z0-9]",
        r"eyJ[a-zA-Z0-9]",
        r"-----BEGIN.*PRIVATE KEY",
        r"api[_-]?key",
        r"secret[_-]?key",
        r"password\s*[=:]",
        r"mongodb\+srv://",
        r"Bearer\s+[a-zA-Z0-9]",
    ]

    for pattern in quick_patterns:
        if re.search(pattern, text, re.IGNORECASE):
            return True

    return False


def mask_credentials_in_text(text: str) -> str:
    """
    Mask detected credentials in text.

    Args:
        text: The text containing credentials

    Returns:
        Text with credentials replaced by masked versions
    """
    detections = detect_credentials(text)
    masked = text
    offset = 0

    for detection in detections:
        start = detection.start_index + offset
        end = detection.end_index + offset
        masked_value = _mask_value(detection.value)
        masked = masked[:start] + masked_value + masked[end:]
        offset += len(masked_value) - (detection.end_index - detection.start_index)

    return masked


def _mask_value(value: str) -> str:
    """Mask a credential value"""
    if len(value) <= 8:
        return "*" * len(value)
    return value[:4] + "*" * min(len(value) - 8, 20) + value[-4:]


def get_detection_stats(results: list[DetectionResult]) -> dict:
    """
    Get statistics about detection results.

    Args:
        results: List of DetectionResult objects

    Returns:
        Dictionary with detection statistics
    """
    by_type: dict[str, int] = {}

    for result in results:
        type_str = result.type.value if hasattr(result.type, 'value') else str(result.type)
        by_type[type_str] = by_type.get(type_str, 0) + 1

    return {
        "total": len(results),
        "by_type": by_type,
        "high_confidence": sum(1 for r in results if r.confidence >= 0.9),
        "average_confidence": sum(r.confidence for r in results) / len(results) if results else 0,
    }
