from __future__ import annotations

import asyncio
from dataclasses import dataclass
from datetime import datetime, timezone
from threading import Lock
from typing import Any, Literal, Protocol

from cachetools import TTLCache

from common.dao.clip import ClipDAO
from common.dao.user import UserDAO
from common.logging import get_logger
from common.models.common import Platform

try:
    from posthog import Posthog
except ImportError:  # pragma: no cover - exercised when dependency is unavailable
    Posthog = None  # type: ignore[assignment]

logger = get_logger(__name__)

DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com"
ACCOUNT_GROUP_TYPE = "account"
UserRole = Literal["owner", "tenant"]


def user_distinct_id(user_id: int) -> str:
    """Return the canonical PostHog distinct id for a user."""
    return f"user:{int(user_id)}"


def account_group_key(owner_user_id: int) -> str:
    """Return the canonical PostHog group key for an account."""
    return f"account:{int(owner_user_id)}"


def normalize_platform_name(platform: Any) -> str:
    """Normalize platform identifiers to the analytics taxonomy."""
    value = platform.value if isinstance(platform, Platform) else str(platform or "")
    normalized = value.strip().lower()
    if normalized == Platform.DIRECT.value:
        return "tldrdirect"
    return normalized


@dataclass(frozen=True)
class AnalyticsIdentityContext:
    """Identity context for analytics events."""

    actor_user_id: int
    owner_user_id: int
    user_role: UserRole = "owner"

    @classmethod
    def for_owner_account(
        cls, user_id: int, *, user_role: UserRole = "owner"
    ) -> "AnalyticsIdentityContext":
        return cls(
            actor_user_id=int(user_id), owner_user_id=int(user_id), user_role=user_role
        )

    @property
    def distinct_id(self) -> str:
        return user_distinct_id(self.actor_user_id)

    @property
    def account_group_key(self) -> str:
        return account_group_key(self.owner_user_id)

    @property
    def groups(self) -> dict[str, str]:
        return {ACCOUNT_GROUP_TYPE: self.account_group_key}


class AnalyticsEnrichmentResolver(Protocol):
    """Protocol for account-level event enrichment."""

    async def resolve(self, identity: AnalyticsIdentityContext) -> dict[str, Any]:
        """Return event properties to attach to the analytics event."""


class NoOpAnalyticsEnrichmentResolver:
    """Resolver that returns no enrichment."""

    async def resolve(self, identity: AnalyticsIdentityContext) -> dict[str, Any]:
        return {}


class AccountAnalyticsEnrichmentResolver:
    """Short-TTL account enrichment resolver for PostHog events."""

    def __init__(
        self,
        *,
        user_dao: UserDAO,
        clip_dao: ClipDAO,
        ttl_seconds: int = 60,
        maxsize: int = 2048,
    ) -> None:
        self._user_dao = user_dao
        self._clip_dao = clip_dao
        self._cache: TTLCache[int, dict[str, Any]] = TTLCache(
            maxsize=maxsize, ttl=ttl_seconds
        )
        self._lock = asyncio.Lock()

    async def resolve(self, identity: AnalyticsIdentityContext) -> dict[str, Any]:
        owner_user_id = int(identity.owner_user_id)
        cached = self._cache.get(owner_user_id)
        if cached is not None:
            return dict(cached)

        async with self._lock:
            cached = self._cache.get(owner_user_id)
            if cached is not None:
                return dict(cached)

            owner_user = await self._user_dao.get_user_by_id(owner_user_id)
            clips_generated_lifetime = await self._clip_dao.count_user_clips_lifetime(
                owner_user_id
            )

            connected_platforms = sorted(
                {
                    normalize_platform_name(Platform.TWITCH)
                    for account in [owner_user.twitch_account]
                    if account is not None
                }
                | {
                    normalize_platform_name(Platform.KICK)
                    for account in [owner_user.kick_account]
                    if account is not None
                }
                | {
                    normalize_platform_name(platform)
                    for platform in (owner_user.social_media_platforms or [])
                    if platform
                }
            )

            created_at = owner_user.created_at
            if created_at.tzinfo is None:
                created_at = created_at.replace(tzinfo=timezone.utc)
            else:
                created_at = created_at.astimezone(timezone.utc)

            enrichment = {
                "connected_platforms": connected_platforms,
                "clips_generated_lifetime": int(clips_generated_lifetime),
                "account_age_days": max(
                    0, (datetime.now(timezone.utc) - created_at).days
                ),
            }
            self._cache[owner_user_id] = dict(enrichment)
            return enrichment

    def invalidate(self, owner_user_id: int) -> None:
        """Invalidate a cached account enrichment record."""
        self._cache.pop(int(owner_user_id), None)


class _NoOpPosthogClient:
    """Small no-op client used when PostHog is disabled or unconfigured."""

    disable_geoip = True

    def capture(self, *args: Any, **kwargs: Any) -> None:
        return None

    def set(self, *args: Any, **kwargs: Any) -> None:
        return None

    def set_once(self, *args: Any, **kwargs: Any) -> None:
        return None

    def group_identify(self, *args: Any, **kwargs: Any) -> None:
        return None

    def shutdown(self) -> None:
        return None


class AnalyticsTracker:
    """Thin PostHog wrapper that centralizes event identity and enrichment."""

    def __init__(
        self,
        *,
        service_name: str,
        client: Any | None,
        enrichment_resolver: AnalyticsEnrichmentResolver | None = None,
        enabled: bool = False,
    ) -> None:
        self.service_name = service_name
        self._client = client or _NoOpPosthogClient()
        self._enrichment_resolver = (
            enrichment_resolver or NoOpAnalyticsEnrichmentResolver()
        )
        self.enabled = bool(enabled and client is not None)

    async def _core_properties(
        self, identity: AnalyticsIdentityContext
    ) -> dict[str, Any]:
        enrichment = await self._enrichment_resolver.resolve(identity)
        return {
            **enrichment,
            "user_id": int(identity.actor_user_id),
            "user_role": identity.user_role,
            "account_id": int(identity.owner_user_id),
        }

    async def capture(
        self,
        event: str,
        *,
        identity: AnalyticsIdentityContext,
        properties: dict[str, Any] | None = None,
    ) -> None:
        if not self.enabled:
            return

        try:
            payload = {
                **(properties or {}),
                **(await self._core_properties(identity)),
            }
            self._client.capture(
                event,
                distinct_id=identity.distinct_id,
                properties=payload,
                groups=identity.groups,
                disable_geoip=True,
            )
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "PostHog capture failed",
                extra={
                    "service": self.service_name,
                    "event": event,
                    "distinct_id": identity.distinct_id,
                    "error": str(exc),
                },
            )

    async def identify(
        self,
        *,
        identity: AnalyticsIdentityContext,
        properties: dict[str, Any] | None = None,
        set_once: dict[str, Any] | None = None,
    ) -> None:
        if not self.enabled:
            return

        try:
            merged_properties = {
                **(properties or {}),
                **(await self._core_properties(identity)),
            }
            self._client.set(
                distinct_id=identity.distinct_id,
                properties=merged_properties,
                disable_geoip=True,
            )
            if set_once:
                self._client.set_once(
                    distinct_id=identity.distinct_id,
                    properties=set_once,
                    disable_geoip=True,
                )
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "PostHog identify failed",
                extra={
                    "service": self.service_name,
                    "distinct_id": identity.distinct_id,
                    "error": str(exc),
                },
            )

    async def group_identify(
        self,
        *,
        identity: AnalyticsIdentityContext,
        properties: dict[str, Any] | None = None,
    ) -> None:
        if not self.enabled:
            return

        try:
            payload = {
                **(properties or {}),
                "account_id": int(identity.owner_user_id),
            }
            self._client.group_identify(
                ACCOUNT_GROUP_TYPE,
                identity.account_group_key,
                payload,
                disable_geoip=True,
            )
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "PostHog group identify failed",
                extra={
                    "service": self.service_name,
                    "group_key": identity.account_group_key,
                    "error": str(exc),
                },
            )

    def shutdown(self) -> None:
        try:
            self._client.shutdown()
        except Exception as exc:  # pragma: no cover - defensive logging
            logger.warning(
                "PostHog shutdown failed",
                extra={"service": self.service_name, "error": str(exc)},
            )


_tracker_lock = Lock()
_tracker_instance: AnalyticsTracker | None = None


def configure_posthog_client(
    *,
    enabled: bool,
    project_api_key: str | None,
    host: str | None,
    service_name: str,
    enrichment_resolver: AnalyticsEnrichmentResolver | None = None,
) -> AnalyticsTracker:
    """Configure and cache the process-wide PostHog tracker."""
    global _tracker_instance
    with _tracker_lock:
        if _tracker_instance is not None:
            _tracker_instance.shutdown()

        if enabled and project_api_key and Posthog is not None:
            client = Posthog(
                project_api_key,
                host=host or DEFAULT_POSTHOG_HOST,
                disable_geoip=True,
            )
            _tracker_instance = AnalyticsTracker(
                service_name=service_name,
                client=client,
                enrichment_resolver=enrichment_resolver,
                enabled=True,
            )
        else:
            if enabled and not project_api_key:
                logger.warning(
                    "PostHog is enabled but no project API key was provided",
                    extra={"service": service_name},
                )
            _tracker_instance = AnalyticsTracker(
                service_name=service_name,
                client=None,
                enrichment_resolver=enrichment_resolver,
                enabled=False,
            )

        return _tracker_instance


def get_posthog_client() -> AnalyticsTracker:
    """Return the configured PostHog tracker or a disabled tracker."""
    global _tracker_instance
    with _tracker_lock:
        if _tracker_instance is None:
            _tracker_instance = AnalyticsTracker(
                service_name="unconfigured",
                client=None,
                enrichment_resolver=NoOpAnalyticsEnrichmentResolver(),
                enabled=False,
            )
        return _tracker_instance


def shutdown_posthog_client() -> None:
    """Flush and shutdown the process-wide PostHog tracker."""
    global _tracker_instance
    with _tracker_lock:
        if _tracker_instance is not None:
            _tracker_instance.shutdown()
        _tracker_instance = None
