"""Stripe webhook event processing."""

import base64
import json
import logging
from datetime import datetime
from decimal import Decimal
from typing import Any

try:
    import stripe

    HAS_STRIPE = True
except ImportError:
    stripe = None  # type: ignore[assignment]
    HAS_STRIPE = False

try:
    from botocore.exceptions import ClientError

    HAS_BOTOCORE = True
except ImportError:
    HAS_BOTOCORE = False

from .credit_manager import CreditManager

logger = logging.getLogger(__name__)

INITIAL_SUBSCRIPTION_CREDITS = 1000
RENEWAL_CREDITS = 1000


class WebhookProcessor:
    """Process Stripe webhook events."""

    def __init__(self, webhook_secret: str, credit_manager: CreditManager):
        """Initialize with webhook secret and credit manager."""
        self.webhook_secret = webhook_secret
        self.credit_manager = credit_manager

    def verify_and_parse(self, payload: str, signature: str) -> dict[str, Any]:
        """Verify webhook signature and parse event."""
        if not HAS_STRIPE or not stripe:
            raise ImportError("stripe package not installed")

        try:
            event = stripe.Webhook.construct_event(payload, signature, self.webhook_secret)
            return dict(event)
        except ValueError as e:
            logger.error(f"Invalid webhook payload: {e}")
            raise
        except stripe.error.SignatureVerificationError as e:
            logger.error(f"Invalid webhook signature: {e}")
            raise

    def process_event(self, event: dict[str, Any]) -> dict[str, Any]:
        """
        Process a verified webhook event.
        Returns response data.
        """
        event_type = event.get("type")
        event_data = event.get("data", {}).get("object", {})

        logger.info(f"Processing webhook event: {event_type}")

        if event_type == "payment_intent.succeeded":
            return self._handle_payment_intent_succeeded(event_data)

        elif event_type == "checkout.session.completed":
            return self._handle_checkout_completed(event_data)

        elif event_type == "customer.subscription.created":
            return self._handle_subscription_created(event_data)

        elif event_type == "customer.subscription.updated":
            return self._handle_subscription_updated(event_data)

        elif event_type == "customer.subscription.deleted":
            return self._handle_subscription_deleted(event_data)

        elif event_type == "invoice.payment_succeeded":
            return self._handle_invoice_paid(event_data)

        elif event_type == "invoice.payment_failed":
            return self._handle_invoice_failed(event_data)

        elif event_type == "charge.dispute.created":
            return self._handle_dispute_created(event_data)

        else:
            logger.info(f"Unhandled event type: {event_type}")
            return {"message": f"Event {event_type} received but not processed"}

    def process_eventbridge_event(self, event: dict[str, Any]) -> dict[str, Any]:
        """Process a Stripe event delivered via EventBridge.

        EventBridge events are pre-verified by AWS, so no signature
        verification is needed. The Stripe event is in event["detail"].
        Falls back to parsing event["body"] for direct webhook calls.
        """
        detail = event.get("detail", {})

        if detail and "type" in detail:
            return self.process_event(detail)

        # Fallback: direct webhook call with body
        body = event.get("body", "")
        if event.get("isBase64Encoded") and body:
            body = base64.b64decode(body).decode("utf-8")

        if body:
            stripe_event = json.loads(body)
            return self.process_event(stripe_event)

        logger.warning("EventBridge event has no detail or body")
        return {"message": "No event data found"}

    def _handle_checkout_completed(self, session: dict[str, Any]) -> dict[str, Any]:
        """Handle successful checkout session for credit purchase."""
        metadata = session.get("metadata", {})
        user_id = metadata.get("user_id")

        if not user_id:
            logger.error("No user_id in checkout session metadata")
            return {"error": "Missing user_id"}

        if session.get("mode") == "payment":
            credits = int(metadata.get("credits", 0))

            if credits > 0:
                new_balance = self.credit_manager.add_credits(user_id, credits)
                logger.info(
                    f"Added {credits} credits to user {user_id}, new balance: {new_balance}"
                )
                return {"credits_added": credits, "new_balance": new_balance}

        return {"message": "Checkout processed"}

    def _handle_subscription_created(self, subscription: dict[str, Any]) -> dict[str, Any]:
        """Handle new subscription creation with initial credit grant."""
        metadata = subscription.get("metadata", {})
        user_id = metadata.get("user_id")
        customer_id = subscription.get("customer")
        subscription_id = subscription.get("id")
        status = str(subscription.get("status", "unknown"))

        if not user_id:
            logger.warning("No user_id in subscription metadata")
            return {"subscription_id": subscription_id, "status": status}

        self.credit_manager.set_subscription_state(
            user_id=user_id,
            status=status,
            stripe_customer_id=customer_id,
            stripe_subscription_id=subscription_id,
        )

        # Grant initial credits
        new_balance = self.credit_manager.add_credits(user_id, INITIAL_SUBSCRIPTION_CREDITS)
        logger.info(
            f"Created subscription {subscription_id} for user {user_id}, "
            f"granted {INITIAL_SUBSCRIPTION_CREDITS} credits, balance: {new_balance}"
        )

        # Set initial last_credited_period
        current_period_start = subscription.get("current_period_start")
        if current_period_start and self.credit_manager.table:
            try:
                self.credit_manager.table.update_item(
                    Key={"user_id": user_id},
                    UpdateExpression="SET last_credited_period = :period",
                    ExpressionAttributeValues={
                        ":period": Decimal(str(current_period_start)),
                    },
                )
            except Exception as e:
                logger.error(f"Error setting last_credited_period for {user_id}: {e}")

        return {
            "subscription_id": subscription_id,
            "status": status,
            "credits_added": INITIAL_SUBSCRIPTION_CREDITS,
            "new_balance": new_balance,
        }

    def _handle_subscription_updated(self, subscription: dict[str, Any]) -> dict[str, Any]:
        """Handle subscription updates with renewal credit logic.

        When a billing period changes (detected via current_period_start),
        grants renewal credits using conditional update to prevent duplicates.
        """
        metadata = subscription.get("metadata", {})
        user_id = metadata.get("user_id")
        subscription_id = subscription.get("id")
        status = str(subscription.get("status", "unknown"))

        if not user_id:
            return {"subscription_id": subscription_id, "status": status}

        # Always update subscription status
        self.credit_manager.set_subscription_state(
            user_id=user_id, status=status, stripe_subscription_id=subscription_id
        )

        credits_added = 0
        current_period_start = subscription.get("current_period_start")

        # Check for renewal credit grant
        if status == "active" and current_period_start and self.credit_manager.table:
            credits_added = self._try_renewal_credit_grant(user_id, current_period_start)

        logger.info(
            f"Updated subscription {subscription_id} status to {status}"
            + (f", granted {credits_added} renewal credits" if credits_added else "")
        )

        result: dict[str, Any] = {
            "subscription_id": subscription_id,
            "status": status,
        }
        if credits_added:
            result["credits_added"] = credits_added
        return result

    def _try_renewal_credit_grant(self, user_id: str, current_period_start: int) -> int:
        """Attempt to grant renewal credits for a new billing period.

        Uses conditional update on last_credited_period to prevent
        duplicate grants from multiple webhook deliveries.

        Returns the number of credits granted (0 if already processed).
        """
        if not self.credit_manager.table:
            return 0

        try:
            # Read current last_credited_period
            response = self.credit_manager.table.get_item(Key={"user_id": user_id})
            item = response.get("Item", {})
            last_period = item.get("last_credited_period")

            period_decimal = Decimal(str(current_period_start))

            # Check if this is a new period
            if last_period is not None and period_decimal <= Decimal(str(last_period)):
                logger.info(f"Period {current_period_start} already credited for {user_id}")
                return 0

            # Conditional update to prevent race conditions
            if last_period is None:
                condition = "attribute_not_exists(last_credited_period)"
                expr_values = {":new_period": period_decimal}
            else:
                condition = "last_credited_period = :prev_period"
                expr_values = {
                    ":new_period": period_decimal,
                    ":prev_period": Decimal(str(last_period)),
                }

            self.credit_manager.table.update_item(
                Key={"user_id": user_id},
                UpdateExpression="SET last_credited_period = :new_period",
                ConditionExpression=condition,
                ExpressionAttributeValues=expr_values,
            )

            # Grant renewal credits
            self.credit_manager.add_credits(user_id, RENEWAL_CREDITS)
            logger.info(
                f"Granted {RENEWAL_CREDITS} renewal credits to {user_id} "
                f"for period {current_period_start}"
            )
            return RENEWAL_CREDITS

        except ClientError as e:
            if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
                logger.info(
                    f"Renewal credits already granted for period "
                    f"{current_period_start} for {user_id} (race condition)"
                )
                return 0
            logger.error(f"Error granting renewal credits for {user_id}: {e}")
            raise
        except Exception as e:
            logger.error(f"Error in renewal credit grant for {user_id}: {e}")
            return 0

    def _handle_subscription_deleted(self, subscription: dict[str, Any]) -> dict[str, Any]:
        """Handle subscription cancellation with timestamp."""
        metadata = subscription.get("metadata", {})
        user_id = metadata.get("user_id")
        subscription_id = subscription.get("id")

        if user_id:
            self.credit_manager.set_subscription_state(
                user_id=user_id, status="cancelled", stripe_subscription_id=subscription_id
            )

            # Set cancellation timestamp
            if self.credit_manager.table:
                try:
                    self.credit_manager.table.update_item(
                        Key={"user_id": user_id},
                        UpdateExpression="SET subscription_cancelled_at = :now",
                        ExpressionAttributeValues={
                            ":now": datetime.utcnow().isoformat(),
                        },
                    )
                except Exception as e:
                    logger.error(f"Error setting cancelled_at for {user_id}: {e}")

            logger.info(f"Cancelled subscription {subscription_id} for user {user_id}")

        return {"subscription_id": subscription_id, "status": "cancelled"}

    def _handle_invoice_paid(self, invoice: dict[str, Any]) -> dict[str, Any]:
        """Handle successful subscription payment."""
        customer_id = invoice.get("customer")
        amount = invoice.get("amount_paid", 0) / 100.0
        logger.info(f"Invoice paid: ${amount} from customer {customer_id}")
        return {"amount_paid": amount}

    def _handle_invoice_failed(self, invoice: dict[str, Any]) -> dict[str, Any]:
        """Handle failed subscription payment."""
        customer_id = invoice.get("customer")
        logger.warning(f"Invoice payment failed for customer {customer_id}")
        return {"status": "payment_failed"}

    def _handle_payment_intent_succeeded(self, payment_intent: dict[str, Any]) -> dict[str, Any]:
        """Handle successful payment intent with idempotent credit grant."""
        metadata = payment_intent.get("metadata", {})
        user_id = metadata.get("user_id")

        if not user_id:
            logger.error("No user_id in payment_intent metadata")
            return {"error": "Missing user_id"}

        # Check if this is a verification charge ($1)
        if metadata.get("type") == "verification":
            logger.info(f"Verification charge completed for user {user_id}")
            return {"type": "verification", "status": "completed"}

        # Get credits from metadata (set during payment creation)
        credits = int(metadata.get("credits", 0))
        payment_intent_id = payment_intent.get("id", "")

        if credits > 0 and payment_intent_id:
            granted = self.credit_manager.idempotent_credit_grant(
                user_id, credits, payment_intent_id
            )
            if granted:
                new_balance = self.credit_manager.get_balance(user_id)
                logger.info(
                    f"Added {credits} credits to user {user_id}, new balance: {new_balance}"
                )
                return {"credits_added": credits, "new_balance": new_balance}
            else:
                logger.info(f"Payment {payment_intent_id} already processed for {user_id}")
                return {"message": "Payment already processed, credits not added"}

        return {"message": "Payment processed"}

    def _handle_dispute_created(self, dispute: dict[str, Any]) -> dict[str, Any]:
        """Handle charge dispute (mark account as disputed)."""
        charge_id = dispute.get("charge")

        if not charge_id:
            logger.error("No charge_id in dispute")
            return {"error": "Missing charge_id"}

        amount = dispute.get("amount", 0) / 100.0
        reason = dispute.get("reason", "unknown")

        logger.warning(f"Dispute created for charge {charge_id}: ${amount}, reason: {reason}")

        return {"dispute_id": dispute.get("id"), "status": "created", "amount": amount}
