"""Synthetic data generation for privacy-safe testing.

Generates realistic synthetic data that matches the shape and patterns
of real data, useful for:
- Sharing test cases without exposing sensitive data
- Generating test fixtures
- Privacy-compliant debugging
"""

from __future__ import annotations

import json
import random
import re
import string
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, List, Optional


@dataclass
class FieldPattern:
    """Pattern for generating synthetic field values."""

    name: str
    pattern: str  # Regex pattern to match
    generator: Callable[[], Any]  # Generator function
    priority: int = 0  # Higher priority patterns are checked first


# Common patterns for data generation
EMAIL_PATTERN = r"[\w.-]+@[\w.-]+\.\w+"
PHONE_PATTERN = r"\+?[\d\s\-()]{10,}"
IP_PATTERN = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
UUID_PATTERN = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
CREDIT_CARD_PATTERN = r"\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}"
SSN_PATTERN = r"\d{3}-\d{2}-\d{4}"
API_KEY_PATTERN = r"(sk|pk|api)[_-][\w]{20,}"
JWT_PATTERN = r"eyJ[\w-]+\.eyJ[\w-]+\.[\w-]+"
PASSWORD_PATTERN = r"password[\"']?\s*[:=]\s*[\"']?[\w!@#$%^&*]+"


class SyntheticGenerator:
    """Generate synthetic data matching real data patterns.

    The generator learns patterns from real data and produces
    synthetic equivalents that maintain structural integrity
    while replacing sensitive values.
    """

    def __init__(self, seed: Optional[int] = None):
        """Initialize generator.

        Args:
            seed: Random seed for reproducibility
        """
        if seed is not None:
            random.seed(seed)

        self._first_names = [
            "Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry",
            "Ivy", "Jack", "Karen", "Leo", "Mia", "Noah", "Olivia", "Paul",
        ]
        self._last_names = [
            "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
            "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Wilson",
        ]
        self._domains = [
            "example.com", "test.org", "demo.net", "sample.io", "mock.dev",
        ]
        self._words = [
            "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta",
            "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi",
        ]
        self._streets = [
            "Main St", "Oak Ave", "Park Blvd", "Cedar Ln", "Elm Dr", "Pine Rd",
        ]
        self._cities = [
            "Springfield", "Riverside", "Greenville", "Franklin", "Clinton",
        ]

        # Track generated values for consistency
        self._value_map: Dict[str, Any] = {}

    def _random_string(self, length: int, charset: str = string.ascii_lowercase) -> str:
        """Generate a random string."""
        return "".join(random.choice(charset) for _ in range(length))

    def generate_email(self) -> str:
        """Generate a synthetic email address."""
        first = random.choice(self._first_names).lower()
        last = random.choice(self._last_names).lower()
        domain = random.choice(self._domains)
        suffix = random.randint(1, 99)
        return f"{first}.{last}{suffix}@{domain}"

    def generate_name(self) -> str:
        """Generate a synthetic full name."""
        return f"{random.choice(self._first_names)} {random.choice(self._last_names)}"

    def generate_phone(self) -> str:
        """Generate a synthetic phone number."""
        area = random.randint(200, 999)
        prefix = random.randint(200, 999)
        line = random.randint(1000, 9999)
        return f"+1-{area}-{prefix}-{line}"

    def generate_ip(self) -> str:
        """Generate a synthetic IP address."""
        # Use private IP ranges
        return f"192.168.{random.randint(0, 255)}.{random.randint(1, 254)}"

    def generate_uuid(self) -> str:
        """Generate a synthetic UUID."""
        return str(uuid.uuid4())

    def generate_credit_card(self) -> str:
        """Generate a synthetic credit card number (test format)."""
        # Generate a clearly fake test card
        return f"4111-1111-1111-{random.randint(1000, 9999)}"

    def generate_ssn(self) -> str:
        """Generate a synthetic SSN (masked)."""
        return f"XXX-XX-{random.randint(1000, 9999)}"

    def generate_api_key(self) -> str:
        """Generate a synthetic API key."""
        prefix = random.choice(["sk_test_", "pk_test_", "api_test_"])
        return prefix + self._random_string(24, string.ascii_letters + string.digits)

    def generate_jwt(self) -> str:
        """Generate a synthetic JWT (clearly marked as fake)."""
        header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
        payload = "eyJzdWIiOiJzeW50aGV0aWMiLCJuYW1lIjoiVGVzdCBVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ"
        sig = self._random_string(43, string.ascii_letters + string.digits + "-_")
        return f"{header}.{payload}.{sig}"

    def generate_address(self) -> Dict[str, str]:
        """Generate a synthetic address."""
        return {
            "street": f"{random.randint(100, 9999)} {random.choice(self._streets)}",
            "city": random.choice(self._cities),
            "state": random.choice(["CA", "NY", "TX", "FL", "IL", "PA"]),
            "zip": f"{random.randint(10000, 99999)}",
            "country": "US",
        }

    def generate_datetime(self, base: Optional[datetime] = None) -> str:
        """Generate a synthetic datetime."""
        if base is None:
            base = datetime.now(timezone.utc)
        offset = timedelta(days=random.randint(-365, 365))
        dt = base + offset
        return dt.isoformat()

    def generate_value(self, value: Any, field_name: str = "") -> Any:
        """Generate a synthetic value matching the type and pattern.

        Args:
            value: Original value to base synthetic on
            field_name: Optional field name for pattern matching

        Returns:
            Synthetic value
        """
        if value is None:
            return None

        # Use cached value for consistency
        cache_key = f"{field_name}:{str(value)[:50]}"
        if cache_key in self._value_map:
            return self._value_map[cache_key]

        result = self._generate_value_internal(value, field_name)
        self._value_map[cache_key] = result
        return result

    def _generate_value_internal(self, value: Any, field_name: str) -> Any:
        """Internal value generation logic."""
        if isinstance(value, str):
            return self._generate_string(value, field_name)
        elif isinstance(value, bool):
            return value  # Preserve booleans
        elif isinstance(value, int):
            return self._generate_int(value, field_name)
        elif isinstance(value, float):
            return self._generate_float(value, field_name)
        elif isinstance(value, list):
            return [self.generate_value(item, field_name) for item in value]
        elif isinstance(value, dict):
            return self.generate_dict(value)
        else:
            return value

    def _generate_string(self, value: str, field_name: str) -> str:
        """Generate a synthetic string."""
        field_lower = field_name.lower()

        # Check field name patterns
        if "email" in field_lower:
            return self.generate_email()
        if "phone" in field_lower or "mobile" in field_lower:
            return self.generate_phone()
        if "name" in field_lower:
            return self.generate_name()
        if "password" in field_lower or "secret" in field_lower:
            return "[REDACTED]"
        if "key" in field_lower or "token" in field_lower:
            return self.generate_api_key()
        if "uuid" in field_lower or "id" in field_lower.split("_"):
            if re.match(UUID_PATTERN, value, re.I):
                return self.generate_uuid()

        # Check value patterns
        if re.match(EMAIL_PATTERN, value):
            return self.generate_email()
        if re.match(PHONE_PATTERN, value):
            return self.generate_phone()
        if re.match(IP_PATTERN, value):
            return self.generate_ip()
        if re.match(UUID_PATTERN, value, re.I):
            return self.generate_uuid()
        if re.match(CREDIT_CARD_PATTERN, value):
            return self.generate_credit_card()
        if re.match(SSN_PATTERN, value):
            return self.generate_ssn()
        if re.match(API_KEY_PATTERN, value, re.I):
            return self.generate_api_key()
        if re.match(JWT_PATTERN, value):
            return self.generate_jwt()

        # Generate similar-length string
        if len(value) > 20:
            # Long strings might be content - truncate and mark
            return f"[synthetic:{self._random_string(8)}]"
        else:
            return self._random_string(len(value))

    def _generate_int(self, value: int, field_name: str) -> int:
        """Generate a synthetic integer."""
        field_lower = field_name.lower()

        # Preserve special values
        if value in (0, 1, -1):
            return value

        # Keep similar magnitude
        if value < 10:
            return random.randint(1, 9)
        elif value < 100:
            return random.randint(10, 99)
        elif value < 1000:
            return random.randint(100, 999)
        else:
            magnitude = len(str(abs(value)))
            low = 10 ** (magnitude - 1)
            high = 10 ** magnitude - 1
            return random.randint(low, high)

    def _generate_float(self, value: float, field_name: str) -> float:
        """Generate a synthetic float."""
        # Keep similar magnitude and precision
        if value == 0:
            return 0.0

        magnitude = abs(value)
        if magnitude < 1:
            return round(random.random(), 4)
        elif magnitude < 100:
            return round(random.uniform(1, 100), 2)
        else:
            return round(random.uniform(100, 10000), 2)

    def generate_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Generate a synthetic dictionary.

        Args:
            data: Original dictionary

        Returns:
            Dictionary with synthetic values
        """
        result = {}
        for key, value in data.items():
            result[key] = self.generate_value(value, key)
        return result

    def generate_transcript(self, transcript: Dict[str, Any]) -> Dict[str, Any]:
        """Generate a synthetic I/O transcript.

        Args:
            transcript: Original transcript

        Returns:
            Transcript with synthetic data
        """
        result: Dict[str, Any] = {}

        # Process HTTP calls
        if "http" in transcript:
            result["http"] = []
            for call in transcript["http"]:
                synthetic_call = {
                    "method": call.get("method"),
                    "url": self._generate_url(call.get("url", "")),
                    "status": call.get("status"),
                    "duration_ms": call.get("duration_ms"),
                }

                if call.get("request_body"):
                    try:
                        body = json.loads(call["request_body"])
                        synthetic_call["request_body"] = json.dumps(self.generate_dict(body))
                    except (json.JSONDecodeError, TypeError):
                        synthetic_call["request_body"] = "[synthetic-body]"

                if call.get("response_body"):
                    try:
                        body = json.loads(call["response_body"])
                        synthetic_call["response_body"] = json.dumps(self.generate_dict(body))
                    except (json.JSONDecodeError, TypeError):
                        synthetic_call["response_body"] = "[synthetic-body]"

                if call.get("headers"):
                    synthetic_call["headers"] = self._generate_headers(call["headers"])

                result["http"].append(synthetic_call)

        # Process SQL calls
        if "sql" in transcript:
            result["sql"] = []
            for call in transcript["sql"]:
                result["sql"].append({
                    "query": self._generate_sql(call.get("query", "")),
                    "args": [self.generate_value(arg, "arg") for arg in call.get("args", [])],
                    "rows": call.get("rows"),
                    "duration_ms": call.get("duration_ms"),
                })

        # Process Redis calls
        if "redis" in transcript:
            result["redis"] = []
            for call in transcript["redis"]:
                result["redis"].append({
                    "command": call.get("command"),
                    "args": [self.generate_value(arg, "arg") for arg in call.get("args", [])],
                    "result": self.generate_value(call.get("result"), "result"),
                    "duration_ms": call.get("duration_ms"),
                })

        return result

    def _generate_url(self, url: str) -> str:
        """Generate a synthetic URL."""
        # Keep structure, replace sensitive parts
        url = re.sub(r"api_key=[\w-]+", "api_key=test_key_xxx", url)
        url = re.sub(r"token=[\w-]+", "token=test_token_xxx", url)
        url = re.sub(r"/users/\d+", f"/users/{random.randint(1000, 9999)}", url)
        return url

    def _generate_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
        """Generate synthetic headers."""
        result = {}
        sensitive_headers = {"authorization", "cookie", "x-api-key", "x-auth-token"}

        for key, value in headers.items():
            if key.lower() in sensitive_headers:
                result[key] = "[REDACTED]"
            else:
                result[key] = value
        return result

    def _generate_sql(self, query: str) -> str:
        """Generate synthetic SQL by masking literal values."""
        # Mask string literals
        query = re.sub(r"'[^']*'", "'[value]'", query)
        # Mask numeric literals in WHERE/VALUES clauses
        query = re.sub(r"= \d+", "= [num]", query)
        return query


@dataclass
class MaskingRule:
    """Rule for masking sensitive data."""

    pattern: str
    replacement: str
    field_names: List[str] = field(default_factory=list)


class DataMasker:
    """Mask sensitive data in transcripts.

    Unlike SyntheticGenerator which creates realistic fake data,
    DataMasker simply redacts sensitive information.
    """

    def __init__(self):
        """Initialize masker with default rules."""
        self.rules: List[MaskingRule] = [
            MaskingRule(EMAIL_PATTERN, "[EMAIL]", ["email"]),
            MaskingRule(PHONE_PATTERN, "[PHONE]", ["phone", "mobile"]),
            MaskingRule(CREDIT_CARD_PATTERN, "[CARD]", ["card", "cc"]),
            MaskingRule(SSN_PATTERN, "[SSN]", ["ssn"]),
            MaskingRule(API_KEY_PATTERN, "[API_KEY]", ["key", "token", "secret"]),
            MaskingRule(JWT_PATTERN, "[JWT]", ["token", "jwt"]),
            MaskingRule(r"password[\"']?\s*[:=]\s*[\"']?[\w!@#$%^&*]+", 'password: "[REDACTED]"', ["password"]),
        ]

    def add_rule(self, pattern: str, replacement: str, field_names: Optional[List[str]] = None) -> None:
        """Add a custom masking rule."""
        self.rules.append(MaskingRule(pattern, replacement, field_names or []))

    def mask(self, data: Any, field_name: str = "") -> Any:
        """Mask sensitive data.

        Args:
            data: Data to mask
            field_name: Optional field name for targeted masking

        Returns:
            Masked data
        """
        if data is None:
            return None

        if isinstance(data, str):
            return self._mask_string(data, field_name)
        elif isinstance(data, dict):
            return {k: self.mask(v, k) for k, v in data.items()}
        elif isinstance(data, list):
            return [self.mask(item, field_name) for item in data]
        else:
            return data

    def _mask_string(self, value: str, field_name: str) -> str:
        """Mask a string value."""
        result = value
        field_lower = field_name.lower()

        for rule in self.rules:
            # Check if field name matches
            if rule.field_names:
                if any(fn in field_lower for fn in rule.field_names):
                    result = re.sub(rule.pattern, rule.replacement, result, flags=re.I)
            else:
                # Apply pattern-based masking
                result = re.sub(rule.pattern, rule.replacement, result, flags=re.I)

        return result

    def mask_transcript(self, transcript: Dict[str, Any]) -> Dict[str, Any]:
        """Mask all sensitive data in a transcript."""
        return self.mask(transcript)


# Convenience functions


def generate_synthetic_transcript(
    transcript: Dict[str, Any],
    seed: Optional[int] = None,
) -> Dict[str, Any]:
    """Generate a synthetic version of a transcript.

    Args:
        transcript: Original I/O transcript
        seed: Random seed for reproducibility

    Returns:
        Synthetic transcript
    """
    generator = SyntheticGenerator(seed=seed)
    return generator.generate_transcript(transcript)


def mask_sensitive_data(data: Any, field_name: str = "") -> Any:
    """Mask sensitive data in any structure.

    Args:
        data: Data to mask
        field_name: Optional field name

    Returns:
        Masked data
    """
    masker = DataMasker()
    return masker.mask(data, field_name)
