import logging
import os
import re
from hashlib import blake2b
from typing import Any, Dict, List, Optional
from uuid import UUID, uuid4

from ..models import TelemetryEvent, UsageEvent, UsageEventType
from .transport import EventStreamTransport
from fmatch.saas.telemetry.constants import TelemetryConfig

log = logging.getLogger(__name__)


class EnhancedPrivacyFilter:
    """
    A class to scrub personally identifiable information (PII) from telemetry events.
    This ensures that sensitive user data is not inadvertently logged.
    """

    # A set of common keys that might contain PII.
    PII_KEYS = {
        "email",
        "name",
        "firstname",
        "lastname",
        "fullname",
        "address",
        "phone",
        "password",
        "token",
        "apikey",
        "secret",
        "licensekey",
        "domain",
        "website",
        "company",
        "account_name",
        "first_name",
        "last_name",
    }
    # A regex to find email-like strings within any value.
    EMAIL_REGEX = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")

    def scrub(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """
        Recursively scrubs a dictionary to remove or obfuscate PII.

        Args:
            payload: The dictionary payload to scrub.

        Returns:
            A new dictionary with PII removed.
        """
        if not isinstance(payload, dict):
            return payload

        scrubbed_payload = {}
        for key, value in payload.items():
            key_lower = key.lower()

            if key_lower in self.PII_KEYS:
                scrubbed_payload[key] = self._scrub_value(value, is_sensitive_key=True)
            elif isinstance(value, dict):
                scrubbed_payload[key] = self.scrub(value)  # Recurse for nested dicts
            elif isinstance(value, list):
                scrubbed_payload[key] = [
                    self.scrub(item)
                    if isinstance(item, dict)
                    else self._scrub_value(item)
                    for item in value
                ]
            else:
                scrubbed_payload[key] = self._scrub_value(value)

        return scrubbed_payload

    def _scrub_value(self, value: Any, is_sensitive_key: bool = False) -> Any:
        """Scrubs a single value based on its type and context."""
        if not isinstance(value, str):
            return value

        if is_sensitive_key:
            return self._hash(value)

        # Scrub email addresses found within other string values
        def replace_email(match):
            return self._hash(match.group())

        return self.EMAIL_REGEX.sub(replace_email, value)

    def _hash(self, value: str) -> str:
        """Hash sensitive data using blake2b with salt and version prefix."""
        salt = os.getenv(TelemetryConfig.salt_env_var, "dev-salt").encode()
        digest = blake2b(value.encode("utf-8"), key=salt, digest_size=16).hexdigest()
        return f"hash:v1:{digest}"

    def _scrub_sensitive(self, event: Dict[str, Any]) -> Dict[str, Any]:
        """Additional scrubbing for sensitive fields."""
        sensitive_keys = {
            "email",
            "domain",
            "website",
            "company",
            "account_name",
            "first_name",
            "last_name",
            "phone",
        }

        for key in sensitive_keys:
            if key in event and isinstance(event[key], str):
                event[key] = self._hash(event[key])

        return event


class BillingCollector:
    """Handles the collection and dispatch of billable usage events."""

    def __init__(self, transport: EventStreamTransport):
        self.transport = transport

    async def collect_usage(
        self,
        account_id: UUID,
        event_type: UsageEventType,
        quantity: int = 1,
        metadata: Optional[Dict[str, Any]] = None,
        idempotency_key: Optional[str] = None,
    ):
        """Creates and sends a usage event."""
        usage_event = UsageEvent(
            account_id=account_id,
            event_type=event_type,
            quantity=quantity,
            metadata=metadata,
            idempotency_key=idempotency_key,
        )
        try:
            await self.transport.send_event(usage_event)
            log.info(
                f"Collected usage event for account {account_id}: {event_type.value} (qty: {quantity})"
            )
        except Exception as e:
            log.error(
                f"Failed to send usage event for account {account_id}: {e}",
                exc_info=True,
            )
            # In a production system, this might go to a dead-letter queue for retry.


class TelemetryCollector:
    """Handles the collection of anonymized telemetry events for product analytics."""

    def __init__(self, transport: Optional[EventStreamTransport] = None):
        self.transport = transport
        self.privacy_filter = EnhancedPrivacyFilter()

    async def collect(
        self,
        events: List[Dict[str, Any]],
        telemetry_opt_in: bool = True,
    ):
        """Collects, scrubs, and sends telemetry events if the user has opted in."""
        if not telemetry_opt_in:
            log.debug("Skipping telemetry events due to opt-out.")
            return

        processed_events = []
        for event in events:
            # Add schema and metadata
            event["schema_version"] = TelemetryConfig.schema_version
            event["client_sdk"] = event.get("client_sdk") or TelemetryConfig.client_sdk
            event["app_version"] = (
                event.get("app_version") or TelemetryConfig.app_version
            )
            event["consent_source"] = (
                event.get("consent_source") or TelemetryConfig.consent_source
            )
            event["trace_id"] = event.get("trace_id") or uuid4().hex

            # Keep idempotency_key behavior as-is
            if "idempotency_key" not in event:
                event["idempotency_key"] = uuid4().hex

            # Scrub sensitive data
            scrubbed = self.privacy_filter.scrub(event)
            scrubbed = self.privacy_filter._scrub_sensitive(scrubbed)
            processed_events.append(scrubbed)

        if self.transport:
            try:
                for event in processed_events:
                    telemetry_event = TelemetryEvent(
                        account_id=event.get("account_id"),
                        user_id=event.get("user_id"),
                        session_id=event.get("session_id"),
                        event_name=event.get("type", "unknown"),
                        payload=event,
                    )
                    await self.transport.send_event(telemetry_event)
                log.debug(f"Collected {len(processed_events)} telemetry events")
            except Exception as e:
                # Fail silently for telemetry, as it's non-critical.
                log.warning(f"Failed to send telemetry events: {e}")
