import Stripe from 'stripe';
import { prisma } from '../../config/database';
import { getStripe, getWebhookSecret } from '../../services/stripe.client';
import { logger } from '../../utils/logger';
import { recordCrmActivity } from '../../utils/crm-activity';
import { paymentEmailService } from './payment-email.service';
import { eventBus } from '../../services/event-bus.service';

// Helper to extract subscription ID from invoice (may be string, object, or missing in newer types)
function getSubscriptionId(invoice: Stripe.Invoice): string | null {
  const raw = invoice as unknown as Record<string, unknown>;
  const sub = raw.subscription;
  if (!sub) return null;
  if (typeof sub === 'string') return sub;
  if (typeof sub === 'object' && sub !== null && 'id' in sub) return (sub as { id: string }).id;
  return null;
}

export const webhookService = {
  /** Verify and parse a webhook event */
  async constructEvent(rawBody: Buffer, signature: string): Promise<Stripe.Event> {
    const stripe = await getStripe();
    const secret = await getWebhookSecret();
    if (!secret) throw new Error('Webhook secret not configured');
    return stripe.webhooks.constructEvent(rawBody, signature, secret);
  },

  /** Route and handle a webhook event */
  async handleEvent(event: Stripe.Event): Promise<void> {
    logger.info(`Stripe webhook: ${event.type} (${event.id})`);

    switch (event.type) {
      case 'checkout.session.completed':
        await this.handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session);
        break;
      case 'invoice.paid':
        await this.handleInvoicePaid(event.data.object as Stripe.Invoice);
        break;
      case 'invoice.payment_failed':
        await this.handleInvoicePaymentFailed(event.data.object as Stripe.Invoice);
        break;
      case 'customer.subscription.updated':
        await this.handleSubscriptionUpdated(event.data.object as Stripe.Subscription);
        break;
      case 'customer.subscription.deleted':
        await this.handleSubscriptionDeleted(event.data.object as Stripe.Subscription);
        break;
      case 'charge.refunded':
        await this.handleChargeRefunded(event.data.object as Stripe.Charge);
        break;
      case 'charge.dispute.created':
        await this.handleDisputeCreated(event.data.object as Stripe.Dispute);
        break;
      case 'charge.dispute.closed':
        await this.handleDisputeClosed(event.data.object as Stripe.Dispute);
        break;
      case 'checkout.session.expired':
        await this.handleCheckoutExpired(event.data.object as Stripe.Checkout.Session);
        break;
      default:
        logger.debug(`Unhandled Stripe event type: ${event.type}`);
    }
  },

  async handleCheckoutCompleted(session: Stripe.Checkout.Session) {
    const type = session.metadata?.type;

    if (type === 'subscription') {
      await this.handleSubscriptionCheckout(session);
    } else if (type === 'product') {
      await this.handleProductCheckout(session);
    } else if (type === 'donation') {
      await this.handleDonationCheckout(session);
    } else if (type === 'event_ticket') {
      await this.handleEventTicketCheckout(session);
    } else {
      logger.warn(`Unknown checkout type: ${type}`);
    }
  },

  async handleSubscriptionCheckout(session: Stripe.Checkout.Session) {
    const { userId, planId } = session.metadata || {};
    if (!userId || !planId) {
      logger.error('Missing metadata in subscription checkout', { sessionId: session.id });
      return;
    }

    const stripe = await getStripe();
    const subscriptionId = typeof session.subscription === 'string'
      ? session.subscription
      : (session.subscription as { id: string } | null)?.id;
    const customerId = typeof session.customer === 'string'
      ? session.customer
      : (session.customer as { id: string } | null)?.id;

    if (!subscriptionId || !customerId) {
      logger.error('Missing subscription or customer ID in checkout session');
      return;
    }

    // Get subscription details from Stripe
    const stripeSub = await stripe.subscriptions.retrieve(subscriptionId) as unknown as {
      current_period_end: number;
      cancel_at_period_end: boolean;
    };
    const currentPeriodEnd = new Date(stripeSub.current_period_end * 1000);

    // Check idempotency
    const existing = await prisma.userSubscription.findUnique({
      where: { stripeSubscriptionId: subscriptionId },
    });
    if (existing) {
      logger.info(`Subscription already exists for ${subscriptionId}`);
      return;
    }

    // Deactivate existing active subs
    await prisma.userSubscription.updateMany({
      where: { userId, status: 'active' },
      data: { status: 'cancelled', cancelledAt: new Date() },
    });

    await prisma.userSubscription.create({
      data: {
        userId,
        planId: parseInt(planId, 10),
        status: 'active',
        startDate: new Date(),
        endDate: currentPeriodEnd,
        stripeSubscriptionId: subscriptionId,
        stripeCustomerId: customerId,
        currentPeriodEnd,
      },
    });

    await this.createAuditLog('subscription_created', { userId, planId, subscriptionId });
    logger.info(`Subscription activated for user ${userId}, plan ${planId}`);

    // Send subscription welcome email (fire-and-forget)
    await paymentEmailService.sendSubscriptionWelcome({
      userId,
      planId: parseInt(planId, 10),
      stripeSubscriptionId: subscriptionId,
      currentPeriodEnd,
    });

    // Sync to Listmonk Subscribers list (fire-and-forget)
    const subUser = await prisma.user.findUnique({ where: { id: userId }, select: { email: true, name: true } });
    const plan = await prisma.subscriptionPlan.findUnique({ where: { id: parseInt(planId, 10) }, select: { name: true } });
    if (subUser) {
      eventBus.publish('payment.subscription.activated', {
        email: subUser.email,
        name: subUser.name || '',
        planName: plan?.name || `Plan ${planId}`,
        subscriptionId,
      });
    }
  },

  async handleProductCheckout(session: Stripe.Checkout.Session) {
    const order = await prisma.order.findUnique({
      where: { stripeCheckoutSessionId: session.id },
    });
    if (!order) {
      logger.error('Order not found for checkout session', { sessionId: session.id });
      return;
    }
    if (order.status === 'COMPLETED') return; // idempotent

    const paymentIntentId = typeof session.payment_intent === 'string'
      ? session.payment_intent
      : (session.payment_intent as { id: string } | null)?.id || null;

    await prisma.$transaction([
      prisma.order.update({
        where: { id: order.id },
        data: {
          status: 'COMPLETED',
          stripePaymentIntentId: paymentIntentId,
          completedAt: new Date(),
        },
      }),
      // Increment purchase count
      ...(order.productId ? [
        prisma.product.update({
          where: { id: order.productId },
          data: { purchaseCount: { increment: 1 } },
        }),
      ] : []),
    ]);

    await this.createAuditLog('product_purchased', {
      orderId: order.id,
      productId: order.productId,
      amount: order.amountCAD,
    });
    logger.info(`Product order completed: ${order.id}`);

    // Send product receipt (fire-and-forget)
    const updatedOrder = await prisma.order.findUnique({
      where: { id: order.id },
      include: { product: { select: { title: true, type: true } } },
    });
    if (updatedOrder) {
      await paymentEmailService.sendProductReceipt({
        id: updatedOrder.id,
        buyerEmail: updatedOrder.buyerEmail || '',
        buyerName: updatedOrder.buyerName,
        amountCAD: updatedOrder.amountCAD,
        completedAt: updatedOrder.completedAt,
        product: updatedOrder.product,
      });

      // Sync to Listmonk Donors list (fire-and-forget)
      if (updatedOrder.buyerEmail) {
        eventBus.publish('payment.product.purchased', {
          email: updatedOrder.buyerEmail,
          name: updatedOrder.buyerName || '',
          productTitle: updatedOrder.product?.title || 'Product',
          amountCents: updatedOrder.amountCAD,
          orderId: updatedOrder.id,
        });
      }

      // CRM activity (fire-and-forget)
      if (updatedOrder.buyerEmail) {
        recordCrmActivity({
          email: updatedOrder.buyerEmail,
          activityType: 'PURCHASE',
          title: `Purchased: ${updatedOrder.product?.title || 'Product'}`,
          metadata: { orderId: updatedOrder.id, productId: updatedOrder.product ? order.productId : null, amountCents: updatedOrder.amountCAD },
        }).catch(() => {});
      }
    }
  },

  async handleDonationCheckout(session: Stripe.Checkout.Session) {
    const order = await prisma.order.findUnique({
      where: { stripeCheckoutSessionId: session.id },
    });
    if (!order) {
      logger.error('Donation order not found for checkout session', { sessionId: session.id });
      return;
    }
    if (order.status === 'COMPLETED') return; // idempotent

    const paymentIntentId = typeof session.payment_intent === 'string'
      ? session.payment_intent
      : (session.payment_intent as { id: string } | null)?.id || null;

    // Link to donation page and/or campaign if metadata contains them
    const donationPageId = session.metadata?.donationPageId || null;
    const campaignId = session.metadata?.campaignId || null;
    const updateData: Record<string, unknown> = {
      status: 'COMPLETED',
      stripePaymentIntentId: paymentIntentId,
      completedAt: new Date(),
    };
    if (donationPageId && !order.donationPageId) {
      updateData.donationPageId = donationPageId;
    }
    if (campaignId && !order.influenceCampaignId) {
      updateData.influenceCampaignId = campaignId;
    }

    await prisma.order.update({
      where: { id: order.id },
      data: updateData as import('@prisma/client').Prisma.OrderUncheckedUpdateInput,
    });

    await this.createAuditLog('donation_completed', {
      orderId: order.id,
      amount: order.amountCAD,
      email: order.buyerEmail,
    });
    logger.info(`Donation completed: ${order.id}, $${(order.amountCAD / 100).toFixed(2)}`);

    // Send donation receipt (fire-and-forget, errors logged but not thrown)
    await paymentEmailService.sendDonationReceipt({
      id: order.id,
      buyerEmail: order.buyerEmail || '',
      buyerName: order.buyerName,
      amountCAD: order.amountCAD,
      donorMessage: order.donorMessage,
      isAnonymous: order.isAnonymous,
      completedAt: new Date(),
    });

    // Sync to Listmonk Donors list (fire-and-forget)
    if (order.buyerEmail) {
      eventBus.publish('payment.donation.completed', {
        email: order.buyerEmail,
        name: order.buyerName || '',
        amountCents: order.amountCAD,
        orderId: order.id,
      });
    }

    // CRM activity (fire-and-forget)
    if (order.buyerEmail) {
      recordCrmActivity({
        email: order.buyerEmail,
        activityType: 'DONATION',
        title: `Donation: $${(order.amountCAD / 100).toFixed(2)}`,
        metadata: { orderId: order.id, amountCents: order.amountCAD },
      }).catch(() => {});
    }
  },

  async handleInvoicePaid(invoice: Stripe.Invoice) {
    const subscriptionId = getSubscriptionId(invoice);
    if (!subscriptionId) return;

    const sub = await prisma.userSubscription.findUnique({
      where: { stripeSubscriptionId: subscriptionId },
    });
    if (!sub) return;

    const stripe = await getStripe();
    const stripeSub = await stripe.subscriptions.retrieve(subscriptionId) as unknown as {
      current_period_end: number;
    };
    const currentPeriodEnd = new Date(stripeSub.current_period_end * 1000);

    await prisma.userSubscription.update({
      where: { id: sub.id },
      data: {
        status: 'active',
        currentPeriodEnd,
        endDate: currentPeriodEnd,
      },
    });

    logger.info(`Invoice paid, subscription ${subscriptionId} renewed to ${currentPeriodEnd}`);
  },

  async handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
    const subscriptionId = getSubscriptionId(invoice);
    if (!subscriptionId) return;

    const sub = await prisma.userSubscription.findUnique({
      where: { stripeSubscriptionId: subscriptionId },
    });
    if (!sub) return;

    await prisma.userSubscription.update({
      where: { id: sub.id },
      data: { status: 'grace_period' },
    });

    await this.createAuditLog('payment_failed', { subscriptionId, userId: sub.userId });
    logger.warn(`Payment failed for subscription ${subscriptionId}`);
  },

  async handleSubscriptionUpdated(subscription: Stripe.Subscription) {
    const sub = await prisma.userSubscription.findUnique({
      where: { stripeSubscriptionId: subscription.id },
    });
    if (!sub) return;

    const rawSub = subscription as unknown as {
      current_period_end: number;
      cancel_at_period_end: boolean;
    };
    const currentPeriodEnd = new Date(rawSub.current_period_end * 1000);

    await prisma.userSubscription.update({
      where: { id: sub.id },
      data: {
        cancelAtPeriodEnd: rawSub.cancel_at_period_end,
        currentPeriodEnd,
        endDate: currentPeriodEnd,
      },
    });
  },

  async handleSubscriptionDeleted(subscription: Stripe.Subscription) {
    const sub = await prisma.userSubscription.findUnique({
      where: { stripeSubscriptionId: subscription.id },
    });
    if (!sub) return;

    await prisma.userSubscription.update({
      where: { id: sub.id },
      data: {
        status: 'cancelled',
        cancelledAt: new Date(),
      },
    });

    await this.createAuditLog('subscription_cancelled', {
      subscriptionId: subscription.id,
      userId: sub.userId,
    });
    logger.info(`Subscription cancelled: ${subscription.id}`);
  },

  async handleEventTicketCheckout(session: Stripe.Checkout.Session) {
    const order = await prisma.order.findUnique({
      where: { stripeCheckoutSessionId: session.id },
    });
    if (!order) {
      logger.error('Order not found for event ticket checkout', { sessionId: session.id });
      return;
    }
    if (order.status === 'COMPLETED') return; // idempotent

    const { eventId, tierId, quantity, buyerEmail, buyerName, userId } = session.metadata || {};
    if (!eventId || !tierId || !quantity) {
      logger.error('Missing metadata in event ticket checkout', { sessionId: session.id });
      return;
    }

    const paymentIntentId = typeof session.payment_intent === 'string'
      ? session.payment_intent
      : (session.payment_intent as { id: string } | null)?.id || null;

    // Complete the order
    await prisma.order.update({
      where: { id: order.id },
      data: {
        status: 'COMPLETED',
        stripePaymentIntentId: paymentIntentId,
        completedAt: new Date(),
      },
    });

    // Create tickets
    try {
      const { ticketsService } = await import('../ticketed-events/tickets.service');
      const { ticketEmailService } = await import('../ticketed-events/ticket-email.service');

      const qty = parseInt(quantity, 10);
      const tickets = await ticketsService.createTickets({
        eventId,
        tierId,
        quantity: qty,
        holderEmail: buyerEmail || order.buyerEmail,
        holderName: buyerName || order.buyerName || undefined,
        userId: userId || order.userId || undefined,
        orderId: order.id,
      });

      // Release reservation (soldCount was incremented by createTickets)
      await prisma.ticketTier.update({
        where: { id: tierId },
        data: { reservedCount: { decrement: qty } },
      });

      // Fetch event + tier for email
      const event = await prisma.ticketedEvent.findUnique({ where: { id: eventId } });
      const tier = await prisma.ticketTier.findUnique({ where: { id: tierId } });

      if (event && tier) {
        for (const ticket of tickets) {
          ticketEmailService.sendTicketConfirmation({
            holderEmail: ticket.holderEmail,
            holderName: ticket.holderName,
            ticketCode: ticket.ticketCode,
            token: (ticket as Record<string, unknown>).token as string,
            eventTitle: event.title,
            eventDate: event.date,
            eventStartTime: event.startTime,
            eventEndTime: event.endTime,
            eventSlug: event.slug,
            venueName: event.venueName,
            venueAddress: event.venueAddress,
            tierName: tier.name,
          }).catch(() => {});
        }
      }

      await this.createAuditLog('event_ticket_purchased', {
        orderId: order.id,
        eventId,
        tierId,
        ticketCount: tickets.length,
        amount: order.amountCAD,
      });
      logger.info(`Event ticket order completed: ${order.id}, ${tickets.length} tickets`);
    } catch (err) {
      logger.error('Failed to create tickets after checkout:', err);
    }
  },

  async handleChargeRefunded(charge: Stripe.Charge) {
    const paymentIntentId = typeof charge.payment_intent === 'string'
      ? charge.payment_intent
      : (charge.payment_intent as { id: string } | null)?.id;
    if (!paymentIntentId) return;

    // Check orders
    const order = await prisma.order.findFirst({
      where: { stripePaymentIntentId: paymentIntentId },
    });
    if (order && order.status !== 'REFUNDED') {
      await prisma.order.update({
        where: { id: order.id },
        data: { status: 'REFUNDED' },
      });
      await this.createAuditLog('order_refunded', { orderId: order.id });

      // If this was an event ticket order, refund the linked tickets
      if (order.type === 'event_ticket') {
        const tickets = await prisma.ticket.findMany({
          where: { orderId: order.id, status: 'VALID' },
        });
        for (const ticket of tickets) {
          await prisma.ticket.update({
            where: { id: ticket.id },
            data: { status: 'REFUNDED' },
          });
          await prisma.ticketTier.update({
            where: { id: ticket.tierId },
            data: { soldCount: { decrement: 1 } },
          });
          await prisma.ticketedEvent.update({
            where: { id: ticket.eventId },
            data: { currentAttendees: { decrement: 1 } },
          });
        }
        if (tickets.length > 0) {
          logger.info(`Refunded ${tickets.length} event tickets for order ${order.id}`);
        }
      }
    }
  },

  async handleDisputeCreated(dispute: Stripe.Dispute) {
    const paymentIntentId = typeof dispute.payment_intent === 'string'
      ? dispute.payment_intent
      : (dispute.payment_intent as { id: string } | null)?.id;
    if (!paymentIntentId) return;

    const order = await prisma.order.findFirst({
      where: { stripePaymentIntentId: paymentIntentId },
    });
    if (!order || order.status === 'DISPUTED') return;

    const previousStatus = order.status;
    await prisma.order.update({
      where: { id: order.id },
      data: { status: 'DISPUTED' },
    });

    // Invalidate event tickets if applicable
    if (order.type === 'event_ticket') {
      const tickets = await prisma.ticket.findMany({
        where: { orderId: order.id, status: 'VALID' },
      });
      for (const ticket of tickets) {
        await prisma.ticket.update({
          where: { id: ticket.id },
          data: { status: 'CANCELLED' },
        });
      }
      if (tickets.length > 0) {
        logger.info(`Invalidated ${tickets.length} tickets for disputed order ${order.id}`);
      }
    }

    await this.createAuditLog('dispute_created', {
      orderId: order.id,
      previousStatus,
      disputeId: dispute.id,
      reason: dispute.reason,
      amount: dispute.amount,
    });
    logger.warn(`Dispute created for order ${order.id}: ${dispute.reason} ($${(dispute.amount / 100).toFixed(2)})`);
  },

  async handleDisputeClosed(dispute: Stripe.Dispute) {
    const paymentIntentId = typeof dispute.payment_intent === 'string'
      ? dispute.payment_intent
      : (dispute.payment_intent as { id: string } | null)?.id;
    if (!paymentIntentId) return;

    const order = await prisma.order.findFirst({
      where: { stripePaymentIntentId: paymentIntentId },
    });
    if (!order) return;

    // Stripe types don't include closed dispute statuses (won/lost/charge_refunded)
    const disputeStatus = dispute.status as string;

    // If dispute was won (resolved in our favor), restore the order + tickets
    if (disputeStatus === 'won') {
      await prisma.order.update({
        where: { id: order.id },
        data: { status: 'COMPLETED' },
      });

      // Restore tickets that were cancelled when the dispute was opened
      if (order.type === 'event_ticket') {
        await prisma.ticket.updateMany({
          where: { orderId: order.id, status: 'CANCELLED' },
          data: { status: 'VALID' },
        });
        logger.info(`Restored tickets for dispute-won order ${order.id}`);
      }

      logger.info(`Dispute won for order ${order.id}, restored to COMPLETED`);
    } else if (disputeStatus === 'lost') {
      // Dispute lost — funds returned to customer
      await prisma.order.update({
        where: { id: order.id },
        data: { status: 'REFUNDED' },
      });
      logger.warn(`Dispute lost for order ${order.id}, marked REFUNDED`);
    } else if (disputeStatus === 'charge_refunded') {
      // Merchant refunded while dispute was in flight — dispute auto-closed
      await prisma.order.update({
        where: { id: order.id },
        data: { status: 'REFUNDED' },
      });
      logger.info(`Dispute closed via refund for order ${order.id}`);
    }

    await this.createAuditLog('dispute_closed', {
      orderId: order.id,
      disputeId: dispute.id,
      outcome: dispute.status,
    });
  },

  async handleCheckoutExpired(session: Stripe.Checkout.Session) {
    // Clean up pending orders for expired checkout sessions
    const order = await prisma.order.findUnique({
      where: { stripeCheckoutSessionId: session.id },
    });
    if (order && order.status === 'PENDING') {
      await prisma.order.update({
        where: { id: order.id },
        data: { status: 'FAILED' },
      });

      // Release ticket reservations if this was an event ticket checkout
      const { tierId, quantity } = session.metadata || {};
      if (tierId && quantity) {
        const qty = parseInt(quantity, 10);
        if (qty > 0) {
          await prisma.ticketTier.update({
            where: { id: tierId },
            data: { reservedCount: { decrement: qty } },
          }).catch((err) => {
            logger.warn(`Failed to release reservation for tier ${tierId}:`, err);
          });
        }
      }

      logger.info(`Checkout expired, order marked failed: ${order.id}`);
    }
  },

  async createAuditLog(action: string, metadata: Record<string, unknown>) {
    try {
      const orderId = typeof metadata.orderId === 'string' ? metadata.orderId : undefined;
      const userId = typeof metadata.userId === 'string' ? metadata.userId : undefined;
      await prisma.paymentAuditLog.create({
        data: {
          action,
          orderId: orderId || null,
          userId: userId || null,
          metadata: metadata as import('@prisma/client').Prisma.InputJsonValue,
        },
      });
      logger.info(`Payment audit: ${action}`, metadata);
    } catch (err) {
      // Audit log failure must not break payment processing
      logger.error('Failed to create audit log', err);
    }
  },
};
