"""Pattern definitions for transcript data analysis.

Defines patterns for detecting anomalies, errors, and suspicious
data in HTTP responses, SQL queries, and other I/O.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from enum import Enum
from re import Pattern
from typing import Any, Callable, Dict, List, Optional


class PatternCategory(Enum):
    """Categories of data patterns."""

    HTTP_ERROR = "http_error"
    HTTP_ANOMALY = "http_anomaly"
    SQL_ERROR = "sql_error"
    SQL_ANOMALY = "sql_anomaly"
    DATA_QUALITY = "data_quality"
    SECURITY = "security"
    PERFORMANCE = "performance"


class PatternSeverity(Enum):
    """Severity levels for detected patterns."""

    CRITICAL = 5
    HIGH = 4
    MEDIUM = 3
    LOW = 2
    INFO = 1


@dataclass
class DataPattern:
    """A pattern to match in transcript data.

    Attributes:
        name: Pattern identifier
        category: Pattern category
        severity: Severity level
        description: Human-readable description
        regex: Optional regex pattern
        matcher: Optional custom matcher function
        evidence_keys: Keys to extract as evidence
    """

    name: str
    category: PatternCategory
    severity: PatternSeverity
    description: str
    regex: Optional[Pattern] = None
    matcher: Optional[Callable[[Any], bool]] = None
    evidence_keys: List[str] = field(default_factory=list)

    def matches(self, data: Any) -> bool:
        """Check if pattern matches the data.

        Args:
            data: Data to check (string, dict, etc.)

        Returns:
            True if pattern matches
        """
        if self.matcher:
            return self.matcher(data)

        if self.regex:
            if isinstance(data, str):
                return bool(self.regex.search(data))
            elif isinstance(data, dict):
                # Check all string values
                for value in data.values():
                    if isinstance(value, str) and self.regex.search(value):
                        return True
        return False


# HTTP Error Patterns
HTTP_ERROR_PATTERNS = [
    DataPattern(
        name="http_5xx",
        category=PatternCategory.HTTP_ERROR,
        severity=PatternSeverity.HIGH,
        description="Server error response (5xx)",
        matcher=lambda r: isinstance(r, dict) and 500 <= r.get("status", 0) < 600,
        evidence_keys=["status", "url", "response_body"],
    ),
    DataPattern(
        name="http_4xx",
        category=PatternCategory.HTTP_ERROR,
        severity=PatternSeverity.MEDIUM,
        description="Client error response (4xx)",
        matcher=lambda r: isinstance(r, dict) and 400 <= r.get("status", 0) < 500,
        evidence_keys=["status", "url", "response_body"],
    ),
    DataPattern(
        name="http_timeout",
        category=PatternCategory.HTTP_ERROR,
        severity=PatternSeverity.HIGH,
        description="HTTP request timeout",
        regex=re.compile(r"(timeout|timed?\s*out|connect.*timeout)", re.IGNORECASE),
        evidence_keys=["url", "duration_ms"],
    ),
    DataPattern(
        name="http_connection_error",
        category=PatternCategory.HTTP_ERROR,
        severity=PatternSeverity.HIGH,
        description="HTTP connection error",
        regex=re.compile(
            r"(connection\s*(refused|reset|error)|network\s*error|"
            r"name\s*resolution|dns\s*error)",
            re.IGNORECASE,
        ),
        evidence_keys=["url", "error_message"],
    ),
]

# HTTP Anomaly Patterns
HTTP_ANOMALY_PATTERNS = [
    DataPattern(
        name="empty_response",
        category=PatternCategory.HTTP_ANOMALY,
        severity=PatternSeverity.MEDIUM,
        description="Empty response body",
        matcher=lambda r: isinstance(r, dict)
        and r.get("status", 0) == 200
        and not r.get("response_body"),
        evidence_keys=["url", "status"],
    ),
    DataPattern(
        name="unexpected_content_type",
        category=PatternCategory.HTTP_ANOMALY,
        severity=PatternSeverity.LOW,
        description="Unexpected content type in response",
        matcher=lambda r: isinstance(r, dict)
        and r.get("expected_content_type")
        and r.get("content_type") != r.get("expected_content_type"),
        evidence_keys=["url", "content_type", "expected_content_type"],
    ),
    DataPattern(
        name="large_response",
        category=PatternCategory.HTTP_ANOMALY,
        severity=PatternSeverity.INFO,
        description="Unusually large response",
        matcher=lambda r: isinstance(r, dict)
        and len(r.get("response_body", "")) > 1_000_000,
        evidence_keys=["url", "response_size"],
    ),
    DataPattern(
        name="slow_response",
        category=PatternCategory.PERFORMANCE,
        severity=PatternSeverity.MEDIUM,
        description="Slow HTTP response (>5s)",
        matcher=lambda r: isinstance(r, dict) and r.get("duration_ms", 0) > 5000,
        evidence_keys=["url", "duration_ms"],
    ),
]

# SQL Error Patterns
SQL_ERROR_PATTERNS = [
    DataPattern(
        name="sql_syntax_error",
        category=PatternCategory.SQL_ERROR,
        severity=PatternSeverity.HIGH,
        description="SQL syntax error",
        regex=re.compile(
            r"(syntax\s*error|near\s*\".*\"|unexpected\s*token)", re.IGNORECASE
        ),
        evidence_keys=["sql_canonical", "error_message"],
    ),
    DataPattern(
        name="sql_constraint_violation",
        category=PatternCategory.SQL_ERROR,
        severity=PatternSeverity.HIGH,
        description="SQL constraint violation",
        regex=re.compile(
            r"(constraint|unique|foreign\s*key|not\s*null).*violation",
            re.IGNORECASE,
        ),
        evidence_keys=["sql_canonical", "error_message"],
    ),
    DataPattern(
        name="sql_deadlock",
        category=PatternCategory.SQL_ERROR,
        severity=PatternSeverity.CRITICAL,
        description="SQL deadlock detected",
        regex=re.compile(r"deadlock", re.IGNORECASE),
        evidence_keys=["sql_canonical", "error_message"],
    ),
    DataPattern(
        name="sql_connection_error",
        category=PatternCategory.SQL_ERROR,
        severity=PatternSeverity.CRITICAL,
        description="Database connection error",
        regex=re.compile(
            r"(connection\s*(refused|closed|error)|"
            r"too\s*many\s*connections|pool\s*exhausted)",
            re.IGNORECASE,
        ),
        evidence_keys=["error_message"],
    ),
]

# SQL Anomaly Patterns
SQL_ANOMALY_PATTERNS = [
    DataPattern(
        name="empty_result",
        category=PatternCategory.SQL_ANOMALY,
        severity=PatternSeverity.LOW,
        description="Query returned no results",
        matcher=lambda r: isinstance(r, dict) and r.get("row_count", -1) == 0,
        evidence_keys=["sql_canonical", "row_count"],
    ),
    DataPattern(
        name="n_plus_one",
        category=PatternCategory.SQL_ANOMALY,
        severity=PatternSeverity.MEDIUM,
        description="Potential N+1 query pattern",
        matcher=lambda r: isinstance(r, dict) and r.get("query_count", 0) > 10,
        evidence_keys=["query_count", "similar_queries"],
    ),
    DataPattern(
        name="slow_query",
        category=PatternCategory.PERFORMANCE,
        severity=PatternSeverity.MEDIUM,
        description="Slow SQL query (>1s)",
        matcher=lambda r: isinstance(r, dict) and r.get("duration_ms", 0) > 1000,
        evidence_keys=["sql_canonical", "duration_ms"],
    ),
    DataPattern(
        name="large_result_set",
        category=PatternCategory.SQL_ANOMALY,
        severity=PatternSeverity.LOW,
        description="Large result set (>1000 rows)",
        matcher=lambda r: isinstance(r, dict) and r.get("row_count", 0) > 1000,
        evidence_keys=["sql_canonical", "row_count"],
    ),
]

# Data Quality Patterns
DATA_QUALITY_PATTERNS = [
    DataPattern(
        name="null_in_required",
        category=PatternCategory.DATA_QUALITY,
        severity=PatternSeverity.MEDIUM,
        description="Null value in required field",
        regex=re.compile(r"\"(id|name|email|user_id)\":\s*null", re.IGNORECASE),
        evidence_keys=["field_name", "record"],
    ),
    DataPattern(
        name="empty_string",
        category=PatternCategory.DATA_QUALITY,
        severity=PatternSeverity.LOW,
        description="Empty string in significant field",
        regex=re.compile(r"\"(name|title|description)\":\s*\"\"", re.IGNORECASE),
        evidence_keys=["field_name", "record"],
    ),
    DataPattern(
        name="invalid_date",
        category=PatternCategory.DATA_QUALITY,
        severity=PatternSeverity.MEDIUM,
        description="Invalid date format",
        regex=re.compile(
            r"\"(date|created_at|updated_at)\":\s*\"(invalid|none|null|0000)\"",
            re.IGNORECASE,
        ),
        evidence_keys=["field_name", "value"],
    ),
    DataPattern(
        name="negative_count",
        category=PatternCategory.DATA_QUALITY,
        severity=PatternSeverity.HIGH,
        description="Negative count value",
        regex=re.compile(r"\"(count|quantity|amount)\":\s*-\d+", re.IGNORECASE),
        evidence_keys=["field_name", "value"],
    ),
]

# Security Patterns
SECURITY_PATTERNS = [
    DataPattern(
        name="exposed_credentials",
        category=PatternCategory.SECURITY,
        severity=PatternSeverity.CRITICAL,
        description="Possible exposed credentials in response",
        regex=re.compile(
            r"(password|secret|api_key|token|credential)\s*[=:]\s*['\"]?[^'\"\\s]+",
            re.IGNORECASE,
        ),
        evidence_keys=["url", "field_name"],
    ),
    DataPattern(
        name="sql_injection_attempt",
        category=PatternCategory.SECURITY,
        severity=PatternSeverity.CRITICAL,
        description="Possible SQL injection in query",
        regex=re.compile(
            r"(--|;|'|\"|\bOR\b|\bAND\b)\s*(1\s*=\s*1|'|\"|--)", re.IGNORECASE
        ),
        evidence_keys=["sql_canonical"],
    ),
    DataPattern(
        name="error_disclosure",
        category=PatternCategory.SECURITY,
        severity=PatternSeverity.MEDIUM,
        description="Stack trace or error details in response",
        regex=re.compile(
            r"(Traceback|at\s+\w+\.\w+\(|Exception\s+in\s+thread|"
            r"File\s+\".*\",\s+line\s+\d+)",
            re.IGNORECASE,
        ),
        evidence_keys=["url", "response_body"],
    ),
]

# All patterns grouped
ALL_PATTERNS: Dict[str, List[DataPattern]] = {
    "http_errors": HTTP_ERROR_PATTERNS,
    "http_anomalies": HTTP_ANOMALY_PATTERNS,
    "sql_errors": SQL_ERROR_PATTERNS,
    "sql_anomalies": SQL_ANOMALY_PATTERNS,
    "data_quality": DATA_QUALITY_PATTERNS,
    "security": SECURITY_PATTERNS,
}


def get_patterns_by_category(category: PatternCategory) -> List[DataPattern]:
    """Get all patterns for a category.

    Args:
        category: Pattern category

    Returns:
        List of patterns in that category
    """
    result = []
    for patterns in ALL_PATTERNS.values():
        for pattern in patterns:
            if pattern.category == category:
                result.append(pattern)
    return result


def get_patterns_by_severity(
    min_severity: PatternSeverity = PatternSeverity.LOW,
) -> List[DataPattern]:
    """Get patterns at or above a severity level.

    Args:
        min_severity: Minimum severity to include

    Returns:
        List of patterns meeting severity threshold
    """
    result = []
    for patterns in ALL_PATTERNS.values():
        for pattern in patterns:
            if pattern.severity.value >= min_severity.value:
                result.append(pattern)
    return result


def match_all_patterns(data: Any) -> List[tuple[DataPattern, Dict[str, Any]]]:
    """Match data against all patterns.

    Args:
        data: Data to match (string, dict, etc.)

    Returns:
        List of (pattern, evidence) tuples for matches
    """
    matches = []
    for patterns in ALL_PATTERNS.values():
        for pattern in patterns:
            if pattern.matches(data):
                evidence = {}
                if isinstance(data, dict):
                    for key in pattern.evidence_keys:
                        if key in data:
                            evidence[key] = data[key]
                matches.append((pattern, evidence))
    return matches


__all__ = [
    "PatternCategory",
    "PatternSeverity",
    "DataPattern",
    "HTTP_ERROR_PATTERNS",
    "HTTP_ANOMALY_PATTERNS",
    "SQL_ERROR_PATTERNS",
    "SQL_ANOMALY_PATTERNS",
    "DATA_QUALITY_PATTERNS",
    "SECURITY_PATTERNS",
    "ALL_PATTERNS",
    "get_patterns_by_category",
    "get_patterns_by_severity",
    "match_all_patterns",
]
