"""
Credit manager for subscription credit tracking.

Primary mode: Uses the AiVibe platform API (api.aivibe.cloud) for atomic
credit deduction and audit trail.

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

from __future__ import annotations

import threading
import time as _time
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from uuid import uuid4

from aicippy.billing.models import CreditTransaction, PlanInfo
from aicippy.config import get_settings
from aicippy.utils.async_utils import run_sync
from aicippy.utils.logging import get_logger

if TYPE_CHECKING:
    from aicippy.billing.plan_validator import PlanValidator
    from aicippy.platform.client import PlatformClient

logger = get_logger(__name__)

# DynamoDB table names (VibeKaro production - legacy fallback)
TENANTS_TABLE = "VibekaroTenants"
CREDITS_TABLE = "VibekaroCredits"


class CreditManager:
    """Manages credit checking and deduction for AiCippy usage.

    Primary mode uses the platform API for atomic credit operations.
    Fallback mode uses DynamoDB conditional updates directly.

    Args:
        plan_validator: PlanValidator instance for plan lookups.
        platform_client: Optional PlatformClient for platform API mode.
    """

    def __init__(
        self,
        plan_validator: PlanValidator,
        platform_client: PlatformClient | None = None,
    ) -> None:
        self._validator = plan_validator
        self._platform_client = platform_client
        settings = get_settings()
        self._credits_per_invocation = settings.credits_per_invocation
        self._session_credits_used: int = 0
        self._session_lock = threading.Lock()
        self._audit_log_failures: int = 0

        # Only initialize DynamoDB resources if no platform client
        self._dynamodb: Any = None
        self._tenants_table: Any = None
        self._credits_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._credits_table = self._dynamodb.Table(CREDITS_TABLE)
            except Exception as e:
                logger.warning("dynamodb_init_failed", error=str(e))

    def check_credits(self, email: str) -> tuple[bool, int]:
        """Check if the user has sufficient credits.

        Args:
            email: User email address.

        Returns:
            Tuple of (has_credits, remaining_count).
            Admin users return (True, -1).
        """
        plan_info = self._validator.validate_or_cached(email)

        if plan_info.is_admin:
            return True, -1

        has_credits = plan_info.credits_remaining >= self._credits_per_invocation
        return has_credits, plan_info.credits_remaining

    def deduct_credit(self, email: str, action: str = "aicippy_chat") -> CreditTransaction:
        """Deduct credits for an AiCippy invocation.

        Uses platform API if available, else DynamoDB conditional update.

        Args:
            email: User email address.
            action: Description of the action consuming credits.

        Returns:
            CreditTransaction with result details.
        """
        plan_info = self._validator.validate_or_cached(email)

        # Admin bypass
        if plan_info.is_admin:
            with self._session_lock:
                self._session_credits_used += self._credits_per_invocation
            return CreditTransaction(
                success=True,
                credits_remaining=-1,
                transaction_id=f"admin-{uuid4().hex[:12]}",
            )

        if not plan_info.tenant_id:
            return CreditTransaction(
                success=False,
                credits_remaining=0,
                error="No tenant found for credit deduction",
            )

        # Use platform API if available, else legacy DynamoDB
        if self._platform_client is not None:
            return self._deduct_via_platform(plan_info, action)
        return self._deduct_via_dynamodb(plan_info, action)

    def _deduct_via_platform(
        self,
        plan_info: PlanInfo,
        action: str,
    ) -> CreditTransaction:
        """Deduct credits via the AiVibe platform API."""
        assert self._platform_client is not None
        try:
            result = run_sync(
                self._platform_client.deduct_credits(
                    tenant_id=plan_info.tenant_id,
                    amount=self._credits_per_invocation,
                    source="aicippy",
                    description=action,
                )
            )

            success = result.get("success", False)
            if success:
                self._validator.clear_cache()
                with self._session_lock:
                    self._session_credits_used += self._credits_per_invocation

                logger.info(
                    "credit_deducted",
                    tenant_id=plan_info.tenant_id,
                    amount=self._credits_per_invocation,
                    remaining=result.get("credits_remaining", 0),
                    action=action,
                    transaction_id=result.get("transaction_id"),
                )

            return CreditTransaction(
                success=success,
                credits_remaining=int(result.get("credits_remaining", 0)),
                transaction_id=result.get("transaction_id"),
                error=result.get("error"),
            )

        except Exception as e:
            logger.exception(
                "credit_deduction_platform_error",
                tenant_id=plan_info.tenant_id,
                error=str(e),
            )
            return CreditTransaction(
                success=False,
                credits_remaining=plan_info.credits_remaining,
                error=f"Credit deduction failed: {e}",
            )

    def _deduct_via_dynamodb(
        self,
        plan_info: PlanInfo,
        action: str,
    ) -> CreditTransaction:
        """Deduct credits via direct DynamoDB update (legacy fallback)."""
        if self._tenants_table is None:
            return CreditTransaction(
                success=False,
                credits_remaining=plan_info.credits_remaining,
                error="No platform client or DynamoDB connection available",
            )

        try:
            from botocore.exceptions import ClientError
        except ImportError:
            return CreditTransaction(
                success=False,
                credits_remaining=plan_info.credits_remaining,
                error="boto3/botocore not available",
            )

        transaction_id = f"aicippy-{uuid4().hex[:12]}"

        try:
            # Atomic conditional deduction on VibekaroTenants
            response = self._tenants_table.update_item(
                Key={"tenantId": plan_info.tenant_id},
                UpdateExpression="SET credits = credits - :amount",
                ConditionExpression="credits >= :amount",
                ExpressionAttributeValues={":amount": self._credits_per_invocation},
                ReturnValues="UPDATED_NEW",
            )

            new_credits = int(response["Attributes"]["credits"])

            # Immediately update local counter under lock after successful DynamoDB update
            with self._session_lock:
                self._session_credits_used += self._credits_per_invocation

            # Log transaction to VibekaroCredits audit table
            audit_item = {
                "tenantId": plan_info.tenant_id,
                "transactionId": transaction_id,
                "type": "debit",
                "amount": self._credits_per_invocation,
                "balance": new_credits,
                "description": action,
                "source": "aicippy",
                "createdAt": datetime.now(UTC).isoformat(),
            }
            for _audit_attempt in range(2):
                try:
                    if self._credits_table is None:
                        raise RuntimeError("Credits audit table not initialized")
                    self._credits_table.put_item(Item=audit_item)
                    break
                except Exception as audit_err:
                    if _audit_attempt == 0:
                        _time.sleep(0.5)
                        continue
                    with self._session_lock:
                        self._audit_log_failures += 1
                    logger.critical(
                        "credit_audit_log_failed",
                        tenant_id=plan_info.tenant_id,
                        transaction_id=transaction_id,
                        error=str(audit_err),
                        error_type=type(audit_err).__name__,
                        audit_failure_count=self._audit_log_failures,
                    )

            # Update local cache with new credit count
            self._validator.clear_cache()

            logger.info(
                "credit_deducted",
                tenant_id=plan_info.tenant_id,
                amount=self._credits_per_invocation,
                remaining=new_credits,
                action=action,
                transaction_id=transaction_id,
            )

            return CreditTransaction(
                success=True,
                credits_remaining=new_credits,
                transaction_id=transaction_id,
            )

        except ClientError as e:
            error_code = e.response.get("Error", {}).get("Code", "")

            if error_code == "ConditionalCheckFailedException":
                logger.warning(
                    "credits_exhausted",
                    tenant_id=plan_info.tenant_id,
                    email=plan_info.email,
                )
                return CreditTransaction(
                    success=False,
                    credits_remaining=0,
                    error="Insufficient credits",
                )

            logger.exception(
                "credit_deduction_error",
                tenant_id=plan_info.tenant_id,
                error=str(e),
            )
            return CreditTransaction(
                success=False,
                credits_remaining=plan_info.credits_remaining,
                error=f"Credit deduction failed: {e}",
            )

    @property
    def session_credits_used(self) -> int:
        """Total credits used in this session."""
        with self._session_lock:
            return self._session_credits_used

    @property
    def audit_log_failures(self) -> int:
        """Total audit log failures in this session."""
        with self._session_lock:
            return self._audit_log_failures

    def get_remaining_credits(self, email: str) -> int:
        """Get the current remaining credit count.

        Returns -1 for admin users.
        """
        plan_info = self._validator.validate_or_cached(email)
        return plan_info.credits_remaining
