"""
Plan validator for subscription enforcement.

Primary mode: Queries the AiVibe platform API (api.aivibe.cloud) for
tenant lookup and plan validation.

Fallback mode: Direct DynamoDB queries against VibeKaro tables when
no PlatformClient is provided (backwards compatibility).

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

Lookup flow (legacy DynamoDB mode):
  email -> VibekaroTenants (email-index GSI) -> tenantId
  tenantId -> VibekaroSubscriptions -> plan + status + expiry
"""

from __future__ import annotations

from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

from aicippy.billing.models import PlanInfo, PlanStatus
from aicippy.config import get_settings, is_founder
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.client import PlatformClient

logger = get_logger(__name__)

# DynamoDB table names (VibeKaro production - legacy fallback)
TENANTS_TABLE = "VibekaroTenants"
SUBSCRIPTIONS_TABLE = "VibekaroSubscriptions"

# GSI on VibekaroTenants for email lookup
TENANTS_EMAIL_INDEX = "email-index"


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

    Uses a 5-minute cache to reduce API/DynamoDB reads. Admin status
    is determined by backend data only:
    - Platform API: is_admin field in validate_plan() response
    - DynamoDB: role="admin" attribute in VibekaroTenants record

    No hardcoded admin email bypass exists in application config.

    Args:
        platform_client: Optional PlatformClient for platform API mode.
            If provided, uses api.aivibe.cloud for validation.
            If None, falls back to direct DynamoDB queries (legacy).
    """

    def __init__(self, platform_client: PlatformClient | None = None) -> None:
        settings = get_settings()
        self._platform_client = platform_client
        self._cache: TTLCache = TTLCache(max_size=1000, ttl_seconds=300)

        # Only initialize DynamoDB resources if no platform client
        self._dynamodb: Any = None
        self._tenants_table: Any = None
        self._subscriptions_table: Any = None
        if platform_client is None:
            try:
                import boto3

                self._dynamodb = boto3.resource("dynamodb", region_name=settings.billing_api_region)
                self._tenants_table = self._dynamodb.Table(TENANTS_TABLE)
                self._subscriptions_table = self._dynamodb.Table(SUBSCRIPTIONS_TABLE)
            except Exception as e:
                logger.warning("dynamodb_init_failed", error=str(e))

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

        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()

        # Founder bypass — unlimited privileges, instant validation
        if is_founder(email_lower):
            return PlanInfo(
                tenant_id="founder",
                org_tenant_id="aivibe",
                email=email_lower,
                plan="chakra",
                status=PlanStatus.ACTIVE,
                credits_remaining=999999,
                credits_total=999999,
                expiry=None,
                is_admin=True,
            )

        # Admin status is determined by backend data:
        # - Platform API: is_admin field in validate_plan() response
        # - DynamoDB: role="admin" attribute in VibekaroTenants record
        # No hardcoded admin bypass in application config.

        # Use platform API if available, else legacy DynamoDB
        if self._platform_client is not None:
            return self._validate_via_platform(email_lower)
        return self._validate_via_dynamodb(email_lower)

    def _validate_via_platform(self, email: str) -> PlanInfo:
        """Validate using the AiVibe platform API."""
        assert self._platform_client is not None
        try:
            tenant_info = run_sync(self._platform_client.get_tenant(email))
            plan_data = run_sync(self._platform_client.validate_plan(tenant_info.tenant_id))

            # Map status string to enum
            try:
                plan_status = PlanStatus(plan_data.get("status", "no_plan"))
            except ValueError:
                plan_status = PlanStatus.SUSPENDED

            # Parse expiry date
            expiry_dt: datetime | None = None
            expiry_str = plan_data.get("expiry")
            if expiry_str:
                try:
                    expiry_dt = datetime.fromisoformat(expiry_str.replace("Z", "+00:00"))
                except (ValueError, AttributeError):
                    logger.debug("expiry_date_parse_failed", exc_info=True)

            # Check if expired based on date
            if (
                plan_status == PlanStatus.ACTIVE
                and expiry_dt is not None
                and expiry_dt < datetime.now(UTC)
            ):
                plan_status = PlanStatus.EXPIRED

            plan_info = PlanInfo(
                tenant_id=tenant_info.tenant_id,
                org_tenant_id=tenant_info.org_tenant_id,
                email=email,
                plan=plan_data.get("plan", "none"),
                status=plan_status,
                credits_remaining=int(plan_data.get("credits_remaining", 0)),
                credits_total=int(plan_data.get("credits_total", 0)),
                expiry=expiry_dt,
                is_admin=bool(plan_data.get("is_admin", False)),
            )
            self._cache.set(email, plan_info)
            return plan_info

        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 cached
            raise PlanValidationError(f"Unable to validate plan: {e}") from e

    def _validate_via_dynamodb(self, email: str) -> PlanInfo:
        """Validate using direct DynamoDB queries (legacy fallback)."""
        if self._tenants_table is None:
            raise PlanValidationError("No platform client or DynamoDB connection available")

        try:
            from botocore.exceptions import ClientError
        except ImportError as e:
            raise PlanValidationError(f"boto3/botocore not available: {e}") from e

        try:
            # Step 1: Query VibekaroTenants by email-index GSI
            tenant_response = self._tenants_table.query(
                IndexName=TENANTS_EMAIL_INDEX,
                KeyConditionExpression="email = :email",
                ExpressionAttributeValues={":email": email},
                Limit=1,
            )

            items = tenant_response.get("Items", [])
            if not items:
                plan_info = PlanInfo(
                    tenant_id="",
                    org_tenant_id="",
                    email=email,
                    plan="none",
                    status=PlanStatus.NO_PLAN,
                    credits_remaining=0,
                    credits_total=0,
                    expiry=None,
                    is_admin=False,
                )
                self._cache.set(email, plan_info)
                return plan_info

            tenant = items[0]
            tenant_id = tenant["tenantId"]
            tenant_credits = int(tenant.get("credits", 0))
            tenant_role = tenant.get("role", "").lower()
            org_tenant_id = tenant.get("orgTenantId", "")

            # Admin role detection from DynamoDB tenant record
            if tenant_role == "admin":
                plan_info = PlanInfo(
                    tenant_id=tenant_id,
                    org_tenant_id=org_tenant_id,
                    email=email,
                    plan="admin",
                    status=PlanStatus.ACTIVE,
                    credits_remaining=-1,
                    credits_total=-1,
                    expiry=None,
                    is_admin=True,
                )
                self._cache.set(email, plan_info)
                return plan_info

            # Org-level admin inheritance: if user is not individually admin
            # but belongs to an org, check if the org record has admin role.
            if org_tenant_id:
                org_response = self._tenants_table.get_item(
                    Key={"tenantId": org_tenant_id},
                )
                org_record = org_response.get("Item")
                if org_record and org_record.get("role", "").lower() == "admin":
                    plan_info = PlanInfo(
                        tenant_id=tenant_id,
                        org_tenant_id=org_tenant_id,
                        email=email,
                        plan="admin",
                        status=PlanStatus.ACTIVE,
                        credits_remaining=-1,
                        credits_total=-1,
                        expiry=None,
                        is_admin=True,
                    )
                    self._cache.set(email, plan_info)
                    return plan_info

            # Step 2: Query VibekaroSubscriptions by tenantId
            sub_response = self._subscriptions_table.get_item(
                Key={"tenantId": tenant_id},
            )

            sub_item = sub_response.get("Item")
            if not sub_item:
                # Tenant exists but no subscription record
                plan_info = PlanInfo(
                    tenant_id=tenant_id,
                    org_tenant_id=org_tenant_id,
                    email=email,
                    plan=tenant.get("plan", "none"),
                    status=PlanStatus.NO_PLAN,
                    credits_remaining=tenant_credits,
                    credits_total=0,
                    expiry=None,
                    is_admin=False,
                )
                self._cache.set(email, plan_info)
                return plan_info

            # Parse subscription details
            plan_name = sub_item.get("plan", "none")
            status_str = sub_item.get("status", "cancelled")
            monthly_credits = int(sub_item.get("monthlyCredits", 0))
            period_end_str = sub_item.get("currentPeriodEnd")

            # Map status string to enum
            try:
                plan_status = PlanStatus(status_str)
            except ValueError:
                plan_status = PlanStatus.SUSPENDED

            # Parse expiry date
            expiry_dt: datetime | None = None
            if period_end_str:
                try:
                    expiry_dt = datetime.fromisoformat(period_end_str.replace("Z", "+00:00"))
                except (ValueError, AttributeError):
                    logger.debug("dynamo_expiry_date_parse_failed", exc_info=True)

            # Check if expired based on date
            if (
                plan_status == PlanStatus.ACTIVE
                and expiry_dt is not None
                and expiry_dt < datetime.now(UTC)
            ):
                plan_status = PlanStatus.EXPIRED

            plan_info = PlanInfo(
                tenant_id=tenant_id,
                org_tenant_id=org_tenant_id,
                email=email,
                plan=plan_name,
                status=plan_status,
                credits_remaining=tenant_credits,
                credits_total=monthly_credits,
                expiry=expiry_dt,
                is_admin=False,
            )
            self._cache.set(email, plan_info)
            return plan_info

        except ClientError as e:
            logger.warning(
                "plan_validation_dynamodb_error",
                email=email,
                error=str(e),
            )
            cached = self._cache.get(email)
            if cached is not None:
                return cached
            raise PlanValidationError(f"Unable to validate plan: {e}") from e

        except Exception as e:
            logger.exception("plan_validation_unexpected_error", email=email, error=str(e))
            cached = self._cache.get(email)
            if cached is not None:
                return cached
            raise PlanValidationError(f"Plan validation failed: {e}") from e

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

        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 and not cached.is_cache_stale:
            return cached

        return self.validate(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 self._cache.get(email.strip().lower())
