"""Plan validator for subscription enforcement.

Validates subscription plans via the AiVibe platform API (api.aivibe.cloud).
All tenant/plan data is stored in PostgreSQL at db.aivibe.cloud.

Lookup flow:
  email -> platform_client.get_tenant(email) -> tenant_id
  tenant_id -> platform_client.validate_plan(tenant_id) -> plan info

Admin status, credits, and plan tier are ALL determined by the platform API.
No hardcoded bypasses exist.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, cast

from aicippy.billing.models import PlanInfo, PlanStatus
from aicippy.exceptions import PlanValidationError
from aicippy.utils.async_utils import TTLCache, run_sync
from aicippy.utils.logging import get_logger

if TYPE_CHECKING:
    from aicippy.platform import PlatformClient

logger = get_logger(__name__)


class PlanValidator:
    """Validates subscription plans for AiCippy access.

    Uses a 5-minute cache to reduce API reads. Admin status is determined
    by the platform API (is_admin field in validate_plan() response).

    Args:
        platform_client: PlatformClient for api.aivibe.cloud validation.

    """

    def __init__(self, platform_client: PlatformClient | None = None) -> None:
        """Initialize the plan validator.

        Args:
            platform_client: The client for platform API requests.

        """
        self._platform_client = platform_client
        self._cache: TTLCache = TTLCache(max_size=1000, ttl_seconds=300)

    def validate(self, email: str) -> PlanInfo:
        """Validate a user's plan by email (sync version).

        Uses run_sync() to bridge async platform API calls. Safe to call
        from sync contexts (typer commands, login flow). For async contexts
        (start_interactive_session), use validate_async() instead.

        Args:
            email: User email address.

        Returns:
            PlanInfo with plan details and validity.

        Raises:
            PlanValidationError: If backend is unreachable and no cache exists.

        """
        email_lower = email.strip().lower()
        return self._validate_via_platform(email_lower)

    async def validate_async(self, email: str) -> PlanInfo:
        """Validate a user's plan by email (async version).

        Calls platform API directly with await. Use this from async contexts
        to avoid event loop conflicts with run_sync().

        Args:
            email: User email address.

        Returns:
            PlanInfo with plan details and validity.

        Raises:
            PlanValidationError: If backend is unreachable and no cache exists.

        """
        email_lower = email.strip().lower()
        return await self._validate_via_platform_async(email_lower)

    def _validate_via_platform(self, email: str) -> PlanInfo:
        """Validate using the AiVibe platform API (sync bridge)."""
        return run_sync(self._validate_via_platform_async(email))

    async def _validate_via_platform_async(self, email: str) -> PlanInfo:
        """Validate using the AiVibe platform API (native async)."""
        if self._platform_client is None:
            raise PlanValidationError("Platform API client not initialized.")  # noqa: TRY003
        try:
            # Use the new GraphQLClient methods
            subscription = await self._platform_client.validate_plan()
            wallet = await self._platform_client.get_credits()
        except Exception as e:
            logger.warning(
                "plan_validation_platform_error",
                email=email,
                error=str(e),
            )
            cached = self._cache.get(email)
            if cached is not None:
                return cast(PlanInfo, cached)
            raise PlanValidationError(f"Unable to validate plan: {e}") from e  # noqa: TRY003

        plan_id = subscription.get("plan", {}).get("planCode", "none")
        # Strict access control: only Chakra plan and Admins (handled via is_admin in subscription or plan)
        is_admin = subscription.get("isAdmin", False)
        is_chakra = plan_id.lower() == "chakra"

        if not (is_admin or is_chakra):
            raise PlanValidationError(f"AiCippy access restricted to Chakra plan. Your plan: {plan_id}")  # noqa: TRY003

        status_str = subscription.get("status", "INACTIVE").lower()
        status = PlanStatus.ACTIVE if status_str == "active" else PlanStatus.SUSPENDED

        credits_remaining = wallet.get("balance", 0)

        plan_info = PlanInfo(
            tenant_id=subscription.get("id", ""),
            org_tenant_id="",  # Not available in new API?
            email=email,
            plan=plan_id,
            status=status,
            credits_remaining=credits_remaining,
            credits_total=credits_remaining,  # Use current balance as total for now
            expiry=None,
            is_admin=is_admin,
        )
        self._cache.set(email, plan_info)
        return plan_info

    def validate_or_cached(self, email: str) -> PlanInfo:
        """Return cached plan info if fresh, otherwise re-validate (sync).

        Args:
            email: User email address.

        Returns:
            PlanInfo (cached if <5min old, fresh otherwise).

        """
        email_lower = email.strip().lower()

        cached = self._cache.get(email_lower)
        if cached is not None:
            plan = cast(PlanInfo, cached)
            if not plan.is_cache_stale:
                return plan

        return self.validate(email_lower)

    async def validate_or_cached_async(self, email: str) -> PlanInfo:
        """Return cached plan info if fresh, otherwise re-validate (async).

        Args:
            email: User email address.

        Returns:
            PlanInfo (cached if <5min old, fresh otherwise).

        """
        email_lower = email.strip().lower()

        cached = self._cache.get(email_lower)
        if cached is not None:
            plan = cast(PlanInfo, cached)
            if not plan.is_cache_stale:
                return plan

        return await self.validate_async(email_lower)

    def clear_cache(self) -> None:
        """Invalidate all cached plan data."""
        self._cache.clear()

    def get_cached(self, email: str) -> PlanInfo | None:
        """Get cached plan info without querying the backend."""
        return cast(PlanInfo | None, self._cache.get(email.strip().lower()))
