# app/handlers/stripe_webhook.py
"""
Stripe webhook event handler.

Processes webhook events from Stripe:
- Subscription lifecycle events
- Invoice and payment events
- Checkout session completion
- Add-on purchases
"""

import logging
from datetime import UTC, datetime

import stripe
from sqlalchemy.orm import Session

from app.modules.billing.models import (
    AddOnProduct,
    BillingHistory,
    MerchantSubscription,
    StoreAddOn,
    StripeWebhookEvent,
    SubscriptionStatus,
    SubscriptionTier,
)
from app.modules.tenancy.models import Store, StorePlatform

logger = logging.getLogger(__name__)


class StripeWebhookHandler:
    """Handler for Stripe webhook events."""

    def __init__(self):
        self.handlers = {
            "checkout.session.completed": self._handle_checkout_completed,
            "customer.subscription.created": self._handle_subscription_created,
            "customer.subscription.updated": self._handle_subscription_updated,
            "customer.subscription.deleted": self._handle_subscription_deleted,
            "invoice.paid": self._handle_invoice_paid,
            "invoice.payment_failed": self._handle_payment_failed,
            "invoice.finalized": self._handle_invoice_finalized,
        }

    def handle_event(self, db: Session, event: stripe.Event) -> dict:
        """
        Process a Stripe webhook event.

        Args:
            db: Database session
            event: Stripe Event object

        Returns:
            Dict with processing result
        """
        event_id = event.id
        event_type = event.type

        # Check for duplicate processing (idempotency)
        existing = (
            db.query(StripeWebhookEvent)
            .filter(StripeWebhookEvent.event_id == event_id)
            .first()
        )

        if existing:
            if existing.status == "processed":
                logger.info(f"Skipping duplicate event {event_id}")
                return {"status": "skipped", "reason": "duplicate"}
            if existing.status == "failed":
                logger.info(f"Retrying previously failed event {event_id}")
        else:
            # Record the event
            webhook_event = StripeWebhookEvent(
                event_id=event_id,
                event_type=event_type,
                status="pending",
            )
            db.add(webhook_event)
            db.flush()
            existing = webhook_event

        # Process the event
        handler = self.handlers.get(event_type)
        if not handler:
            logger.debug(f"No handler for event type {event_type}")
            existing.status = "processed"
            existing.processed_at = datetime.now(UTC)
            db.commit()
            return {"status": "ignored", "reason": f"no handler for {event_type}"}

        try:
            result = handler(db, event)
            existing.status = "processed"
            existing.processed_at = datetime.now(UTC)
            db.commit()
            logger.info(f"Successfully processed event {event_id} ({event_type})")
            return {"status": "processed", "result": result}

        except Exception as e:
            logger.error(f"Error processing event {event_id}: {e}")
            existing.status = "failed"
            existing.error_message = str(e)
            db.commit()
            raise

    # =========================================================================
    # Event Handlers
    # =========================================================================

    def _handle_checkout_completed(
        self, db: Session, event: stripe.Event
    ) -> dict:
        """
        Handle checkout.session.completed event.

        Handles two types of checkouts:
        1. Subscription checkout - Updates MerchantSubscription
        2. Add-on checkout - Creates StoreAddOn record
        """
        session = event.data.object
        store_id = session.metadata.get("store_id")
        addon_code = session.metadata.get("addon_code")

        if not store_id:
            logger.warning(f"Checkout session {session.id} missing store_id")
            return {"action": "skipped", "reason": "no store_id"}

        store_id = int(store_id)

        # Check if this is an add-on purchase
        if addon_code:
            return self._handle_addon_checkout(db, session, store_id, addon_code)

        # Otherwise, handle subscription checkout
        return self._handle_subscription_checkout(db, session, store_id)

    def _handle_subscription_checkout(
        self, db: Session, session, store_id: int
    ) -> dict:
        """Handle subscription checkout completion."""
        # Resolve store_id to merchant_id and platform_id
        store = db.query(Store).filter(Store.id == store_id).first()
        if not store:
            logger.warning(f"No store found for store_id {store_id}")
            return {"action": "skipped", "reason": "no store"}

        merchant_id = store.merchant_id

        sp = (
            db.query(StorePlatform.platform_id)
            .filter(StorePlatform.store_id == store_id)
            .first()
        )
        if not sp:
            logger.warning(f"No platform found for store {store_id}")
            return {"action": "skipped", "reason": "no platform"}

        platform_id = sp[0]

        subscription = (
            db.query(MerchantSubscription)
            .filter(
                MerchantSubscription.merchant_id == merchant_id,
                MerchantSubscription.platform_id == platform_id,
            )
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for merchant {merchant_id}")
            return {"action": "skipped", "reason": "no subscription"}

        # Update subscription with Stripe IDs
        subscription.stripe_customer_id = session.customer
        subscription.stripe_subscription_id = session.subscription
        subscription.status = SubscriptionStatus.ACTIVE.value

        # Get subscription details to set period dates
        if session.subscription:
            stripe_sub = stripe.Subscription.retrieve(session.subscription)
            subscription.period_start = datetime.fromtimestamp(
                stripe_sub.current_period_start, tz=UTC
            )
            subscription.period_end = datetime.fromtimestamp(
                stripe_sub.current_period_end, tz=UTC
            )

            if stripe_sub.trial_end:
                subscription.trial_ends_at = datetime.fromtimestamp(
                    stripe_sub.trial_end, tz=UTC
                )

        logger.info(f"Subscription checkout completed for merchant {merchant_id}")
        return {"action": "activated", "merchant_id": merchant_id}

    def _handle_addon_checkout(
        self, db: Session, session, store_id: int, addon_code: str
    ) -> dict:
        """
        Handle add-on checkout completion.

        Creates a StoreAddOn record for the purchased add-on.
        """
        # Get the add-on product
        addon_product = (
            db.query(AddOnProduct)
            .filter(AddOnProduct.code == addon_code)
            .first()
        )

        if not addon_product:
            logger.error(f"Add-on product '{addon_code}' not found")
            return {"action": "failed", "reason": f"addon '{addon_code}' not found"}

        # Check if store already has this add-on active
        existing_addon = (
            db.query(StoreAddOn)
            .filter(
                StoreAddOn.store_id == store_id,
                StoreAddOn.addon_product_id == addon_product.id,
                StoreAddOn.status == "active",
            )
            .first()
        )

        if existing_addon:
            logger.info(
                f"Store {store_id} already has active add-on {addon_code}, "
                f"updating quantity"
            )
            # For quantity-based add-ons, we could increment
            # For now, just log and return
            return {
                "action": "already_exists",
                "store_id": store_id,
                "addon_code": addon_code,
            }

        # Get domain name from metadata (for domain add-ons)
        domain_name = session.metadata.get("domain_name")
        if domain_name == "":
            domain_name = None

        # Get subscription item ID from Stripe subscription
        stripe_subscription_item_id = None
        if session.subscription:
            try:
                stripe_sub = stripe.Subscription.retrieve(session.subscription)
                if stripe_sub.items.data:
                    # Find the item matching our add-on price
                    for item in stripe_sub.items.data:
                        if item.price.id == addon_product.stripe_price_id:
                            stripe_subscription_item_id = item.id
                            break
            except Exception as e:
                logger.warning(f"Could not retrieve subscription items: {e}")

        # Get period dates from subscription
        period_start = None
        period_end = None
        if session.subscription:
            try:
                stripe_sub = stripe.Subscription.retrieve(session.subscription)
                period_start = datetime.fromtimestamp(
                    stripe_sub.current_period_start, tz=UTC
                )
                period_end = datetime.fromtimestamp(
                    stripe_sub.current_period_end, tz=UTC
                )
            except Exception as e:
                logger.warning(f"Could not retrieve subscription period: {e}")

        # Create StoreAddOn record
        store_addon = StoreAddOn(
            store_id=store_id,
            addon_product_id=addon_product.id,
            status="active",
            domain_name=domain_name,
            quantity=1,  # Default quantity, could be from session line items
            stripe_subscription_item_id=stripe_subscription_item_id,
            period_start=period_start,
            period_end=period_end,
        )
        db.add(store_addon)

        logger.info(
            f"Add-on '{addon_code}' purchased by store {store_id}"
            + (f" for domain {domain_name}" if domain_name else "")
        )

        return {
            "action": "addon_created",
            "store_id": store_id,
            "addon_code": addon_code,
            "addon_id": store_addon.id,
            "domain_name": domain_name,
        }

    def _handle_subscription_created(
        self, db: Session, event: stripe.Event
    ) -> dict:
        """Handle customer.subscription.created event."""
        stripe_sub = event.data.object
        customer_id = stripe_sub.customer

        # Find subscription by customer ID
        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_customer_id == customer_id)
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for customer {customer_id}")
            return {"action": "skipped", "reason": "no subscription"}

        # Update subscription
        subscription.stripe_subscription_id = stripe_sub.id
        subscription.status = self._map_stripe_status(stripe_sub.status)
        subscription.period_start = datetime.fromtimestamp(
            stripe_sub.current_period_start, tz=UTC
        )
        subscription.period_end = datetime.fromtimestamp(
            stripe_sub.current_period_end, tz=UTC
        )

        logger.info(f"Subscription created for merchant {subscription.merchant_id}")
        return {"action": "created", "merchant_id": subscription.merchant_id}

    def _handle_subscription_updated(
        self, db: Session, event: stripe.Event
    ) -> dict:
        """Handle customer.subscription.updated event."""
        stripe_sub = event.data.object

        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_subscription_id == stripe_sub.id)
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for {stripe_sub.id}")
            return {"action": "skipped", "reason": "no subscription"}

        # Update status and period
        subscription.status = self._map_stripe_status(stripe_sub.status)
        subscription.period_start = datetime.fromtimestamp(
            stripe_sub.current_period_start, tz=UTC
        )
        subscription.period_end = datetime.fromtimestamp(
            stripe_sub.current_period_end, tz=UTC
        )

        # Handle cancellation
        if stripe_sub.cancel_at_period_end:
            subscription.cancelled_at = datetime.now(UTC)
            subscription.cancellation_reason = stripe_sub.metadata.get(
                "cancellation_reason", "user_request"
            )
        elif subscription.cancelled_at and not stripe_sub.cancel_at_period_end:
            # Subscription reactivated
            subscription.cancelled_at = None
            subscription.cancellation_reason = None

        # Check for tier change via price
        if stripe_sub.items.data:
            new_price_id = stripe_sub.items.data[0].price.id
            # Look up new tier by Stripe price ID
            tier = (
                db.query(SubscriptionTier)
                .filter(SubscriptionTier.stripe_price_monthly_id == new_price_id)
                .first()
            )
            if tier:
                subscription.tier_id = tier.id
                logger.info(
                    f"Tier changed to {tier.code} for merchant {subscription.merchant_id}"
                )

        # Sync store_platforms based on subscription status
        from app.modules.billing.services.store_platform_sync_service import (
            store_platform_sync,
        )

        active_statuses = {
            SubscriptionStatus.TRIAL.value,
            SubscriptionStatus.ACTIVE.value,
            SubscriptionStatus.PAST_DUE.value,
            SubscriptionStatus.CANCELLED.value,
        }
        store_platform_sync.sync_store_platforms_for_merchant(
            db,
            subscription.merchant_id,
            subscription.platform_id,
            is_active=subscription.status in active_statuses,
        )

        logger.info(f"Subscription updated for merchant {subscription.merchant_id}")
        return {"action": "updated", "merchant_id": subscription.merchant_id}

    def _handle_subscription_deleted(
        self, db: Session, event: stripe.Event
    ) -> dict:
        """
        Handle customer.subscription.deleted event.

        Cancels the subscription and all associated add-ons for the merchant's stores.
        """
        stripe_sub = event.data.object

        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_subscription_id == stripe_sub.id)
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for {stripe_sub.id}")
            return {"action": "skipped", "reason": "no subscription"}

        merchant_id = subscription.merchant_id

        # Cancel the subscription
        subscription.status = SubscriptionStatus.CANCELLED.value
        subscription.cancelled_at = datetime.now(UTC)

        # Find all stores for this merchant, then cancel their add-ons
        store_ids = [
            s.id
            for s in db.query(Store.id)
            .filter(Store.merchant_id == merchant_id)
            .all()
        ]

        cancelled_addons = (
            db.query(StoreAddOn)
            .filter(
                StoreAddOn.store_id.in_(store_ids),
                StoreAddOn.status == "active",
            )
            .all()
        )

        addon_count = 0
        for addon in cancelled_addons:
            addon.status = "cancelled"
            addon.cancelled_at = datetime.now(UTC)
            addon_count += 1

        if addon_count > 0:
            logger.info(f"Cancelled {addon_count} add-ons for merchant {merchant_id}")

        # Deactivate store_platforms for the deleted subscription's platform
        from app.modules.billing.services.store_platform_sync_service import (
            store_platform_sync,
        )

        store_platform_sync.sync_store_platforms_for_merchant(
            db, merchant_id, subscription.platform_id, is_active=False
        )

        logger.info(f"Subscription deleted for merchant {merchant_id}")
        return {
            "action": "cancelled",
            "merchant_id": merchant_id,
            "addons_cancelled": addon_count,
        }

    def _handle_invoice_paid(self, db: Session, event: stripe.Event) -> dict:
        """Handle invoice.paid event."""
        invoice = event.data.object
        customer_id = invoice.customer

        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_customer_id == customer_id)
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for customer {customer_id}")
            return {"action": "skipped", "reason": "no subscription"}

        # Record billing history
        billing_record = BillingHistory(
            merchant_id=subscription.merchant_id,
            stripe_invoice_id=invoice.id,
            stripe_payment_intent_id=invoice.payment_intent,
            invoice_number=invoice.number,
            invoice_date=datetime.fromtimestamp(invoice.created, tz=UTC),
            subtotal_cents=invoice.subtotal,
            tax_cents=invoice.tax or 0,
            total_cents=invoice.total,
            amount_paid_cents=invoice.amount_paid,
            currency=invoice.currency.upper(),
            status="paid",
            invoice_pdf_url=invoice.invoice_pdf,
            hosted_invoice_url=invoice.hosted_invoice_url,
        )
        db.add(billing_record)

        # Reset payment retry count on successful payment
        subscription.payment_retry_count = 0
        subscription.last_payment_error = None

        logger.info(f"Invoice paid for merchant {subscription.merchant_id}")
        return {
            "action": "recorded",
            "merchant_id": subscription.merchant_id,
            "invoice_id": invoice.id,
        }

    def _handle_payment_failed(self, db: Session, event: stripe.Event) -> dict:
        """Handle invoice.payment_failed event."""
        invoice = event.data.object
        customer_id = invoice.customer

        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_customer_id == customer_id)
            .first()
        )

        if not subscription:
            logger.warning(f"No subscription found for customer {customer_id}")
            return {"action": "skipped", "reason": "no subscription"}

        # Update subscription status
        subscription.status = SubscriptionStatus.PAST_DUE.value
        subscription.payment_retry_count = (subscription.payment_retry_count or 0) + 1

        # Store error message
        if invoice.last_payment_error:
            subscription.last_payment_error = invoice.last_payment_error.get("message")

        logger.warning(
            f"Payment failed for merchant {subscription.merchant_id} "
            f"(retry #{subscription.payment_retry_count})"
        )
        return {
            "action": "marked_past_due",
            "merchant_id": subscription.merchant_id,
            "retry_count": subscription.payment_retry_count,
        }

    def _handle_invoice_finalized(
        self, db: Session, event: stripe.Event
    ) -> dict:
        """Handle invoice.finalized event."""
        invoice = event.data.object
        customer_id = invoice.customer

        subscription = (
            db.query(MerchantSubscription)
            .filter(MerchantSubscription.stripe_customer_id == customer_id)
            .first()
        )

        if not subscription:
            return {"action": "skipped", "reason": "no subscription"}

        # Check if we already have this invoice
        existing = (
            db.query(BillingHistory)
            .filter(BillingHistory.stripe_invoice_id == invoice.id)
            .first()
        )

        if existing:
            return {"action": "skipped", "reason": "already recorded"}

        # Record as pending invoice
        billing_record = BillingHistory(
            merchant_id=subscription.merchant_id,
            stripe_invoice_id=invoice.id,
            invoice_number=invoice.number,
            invoice_date=datetime.fromtimestamp(invoice.created, tz=UTC),
            due_date=datetime.fromtimestamp(invoice.due_date, tz=UTC)
            if invoice.due_date
            else None,
            subtotal_cents=invoice.subtotal,
            tax_cents=invoice.tax or 0,
            total_cents=invoice.total,
            amount_paid_cents=0,
            currency=invoice.currency.upper(),
            status="open",
            invoice_pdf_url=invoice.invoice_pdf,
            hosted_invoice_url=invoice.hosted_invoice_url,
        )
        db.add(billing_record)

        return {"action": "recorded_pending", "merchant_id": subscription.merchant_id}

    # =========================================================================
    # Helpers
    # =========================================================================

    def _map_stripe_status(self, stripe_status: str) -> str:
        """Map Stripe subscription status to internal status string."""
        status_map = {
            "active": SubscriptionStatus.ACTIVE.value,
            "trialing": SubscriptionStatus.TRIAL.value,
            "past_due": SubscriptionStatus.PAST_DUE.value,
            "canceled": SubscriptionStatus.CANCELLED.value,
            "unpaid": SubscriptionStatus.PAST_DUE.value,
            "incomplete": SubscriptionStatus.TRIAL.value,  # Treat as trial until complete
            "incomplete_expired": SubscriptionStatus.EXPIRED.value,
        }
        return status_map.get(stripe_status, SubscriptionStatus.EXPIRED.value)


# Create handler instance
stripe_webhook_handler = StripeWebhookHandler()
