import { Request, Response } from "express"
import { addMonthsSafe, stripe, SUBSCRIPTION_PLANS, prisma } from "@codeflash-ai/common"
import * as Sentry from "@sentry/node"
import { logger } from "../utils/logger.js"
import { badRequest } from "../exceptions/index.js"

// Define types for better type safety
type SubscriptionStatus = "active" | "past_due" | "canceled" | "incomplete"
type PlanType = "free" | "pro" | "enterprise"

interface SubscriptionData {
  user_id: string
  stripe_customer_id: string
  stripe_subscription_id: string
  plan_type: PlanType
  optimizations_limit: number
  subscription_status: SubscriptionStatus
  current_period_start: Date
  current_period_end: Date
}

// Dependencies interface for easier testing
export interface StripeWebhookDependencies {
  stripe: typeof stripe
  prisma: typeof prisma
  Sentry: typeof Sentry
  getWebhookSecret: () => string | undefined
}

// Default dependencies
let dependencies: StripeWebhookDependencies = {
  stripe,
  prisma,
  Sentry,
  getWebhookSecret: () => process.env.STRIPE_WEBHOOK_SECRET,
}

// For testing - allow dependency injection
export function setStripeWebhookDependencies(deps: Partial<StripeWebhookDependencies>) {
  dependencies = { ...dependencies, ...deps }
}

export function resetStripeWebhookDependencies() {
  dependencies = {
    stripe,
    prisma,
    Sentry,
    getWebhookSecret: () => process.env.STRIPE_WEBHOOK_SECRET,
  }
}

export async function stripeWebhookHandler(req: Request, res: Response) {
  const sig = req.headers["stripe-signature"]

  try {
    const webhookSecret = dependencies.getWebhookSecret()
    if (!webhookSecret) {
      throw new Error("STRIPE_WEBHOOK_SECRET is not configured")
    }

    const event = dependencies.stripe.webhooks.constructEvent(req.body, sig, webhookSecret)

    logger.info("Processing Stripe webhook", req, {
      eventType: event.type,
      eventId: event.id,
    })

    // Extract useful data for debugging
    const eventObject = event.data.object

    // Handle different event types
    switch (event.type) {
      case "customer.subscription.created":
      case "customer.subscription.updated":
        await handleSubscriptionUpdate(eventObject)
        break

      case "customer.subscription.deleted":
        await handleSubscriptionCancellation(eventObject)
        break

      case "checkout.session.completed":
        await handleCheckoutCompleted(eventObject)
        break

      case "invoice.payment_failed":
        await handleFailedPayment(eventObject)
        break

      // We can acknowledge other events without processing them
      default:
        logger.info("No handler implemented for event type", req, {
          eventType: event.type,
        })
    }

    // Always return 200 to acknowledge receipt
    res.json({ received: true })
  } catch (err: any) {
    logger.errorWithSentry("Webhook Error", req, { errorMessage: err.message }, err)
    dependencies.Sentry.captureException(err)
    throw badRequest(`Webhook Error: ${err.message}`)
  }
}

export async function handleCheckoutCompleted(session: any) {
  // Extract userId from the session metadata
  const userId = session.metadata?.userId
  if (!userId) {
    logger.error(
      "No userId in checkout session metadata",
      {
        operation: "checkout_completed",
      },
      {
        sessionId: session.id,
      },
    )
    return
  }

  logger.info("Processing checkout completion", {
    operation: "checkout_completed",
    userId,
    sessionId: session.id,
    mode: session.mode,
  })

  // If this is a subscription checkout, fetch the subscription
  if (session.mode === "subscription" && session.subscription) {
    // Update the subscription with userId metadata if missing
    try {
      await dependencies.stripe.subscriptions.update(session.subscription, {
        metadata: { userId },
      })

      // Also update customer metadata
      if (session.customer) {
        await dependencies.stripe.customers.update(session.customer, {
          metadata: { userId },
        })
      }

      // Now fetch and process the subscription
      const subscription = await dependencies.stripe.subscriptions.retrieve(session.subscription)
      await handleSubscriptionUpdate(subscription)
    } catch (error) {
      logger.errorWithSentry(
        "Error processing checkout session:",
        {
          operation: "checkout_completed",
          userId,
          sessionId: session.id,
        },
        {},
        error as Error,
      )
      dependencies.Sentry.captureException(error)
    }
  }
}

export async function handleSubscriptionUpdate(subscription: any) {
  // First check subscription metadata
  let userId = subscription.metadata?.userId

  // If not in subscription metadata, check customer metadata
  if (!userId && subscription.customer) {
    try {
      const customer = await dependencies.stripe.customers.retrieve(subscription.customer as string)
      if ("metadata" in customer) {
        userId = customer.metadata?.userId
      }
    } catch (error) {
      logger.error(
        "Error retrieving customer:",
        {
          operation: "subscription_update",
          subscriptionId: subscription.id,
          customerId: subscription.customer,
        },
        {},
        error as Error,
      )
    }
  }

  if (!userId) {
    logger.error(
      "No userId found in subscription or customer metadata",
      {
        operation: "subscription_update",
      },
      {
        subscriptionId: subscription.id,
        customerId: subscription.customer,
      },
    )
    return
  }

  try {
    const priceId = subscription.items.data[0].price.id
    const price = await dependencies.stripe.prices.retrieve(priceId)
    logger.info("Updating subscription", {
      operation: "subscription_update",
      userId,
      subscriptionId: subscription.id,
      priceId,
      planType: price.metadata?.tier || "pro",
    })

    const currentPeriodStart = new Date()
    let currentPeriodEnd
    // Adjust currentPeriodEnd based on interval and interval_count if needed
    if (price.recurring) {
      const interval = price.recurring.interval
      const interval_count = price.recurring.interval_count || 1

      if (interval === "month") {
        currentPeriodEnd = addMonthsSafe(currentPeriodStart, interval_count)
      } else if (interval === "year") {
        currentPeriodEnd = addMonthsSafe(currentPeriodStart, interval_count * 12)
      }
    }

    // Prepare update data
    const updateData: any = {
      stripe_subscription_id: subscription.id,
      plan_type: price.metadata?.tier || "pro",
      optimizations_limit: SUBSCRIPTION_PLANS.PRO.optimizations,
      subscription_status: subscription.status,
      current_period_start: currentPeriodStart,
      current_period_end: currentPeriodEnd,
      optimizations_used: 0,
      updated_at: new Date(),
    }

    // Handle cancel_at_period_end if present in the subscription
    if (subscription.cancel_at_period_end !== undefined) {
      updateData.cancel_at_period_end = subscription.cancel_at_period_end

      // If subscription was just cancelled, record the cancellation date
      if (subscription.cancel_at_period_end === true && !subscription.cancellation_request_date) {
        updateData.cancellation_request_date = new Date()
      }
      // If subscription was reactivated, clear the cancellation date
      else if (subscription.cancel_at_period_end === false) {
        updateData.cancellation_request_date = null
      }
    }

    await dependencies.prisma.subscriptions.upsert({
      where: { user_id: userId },
      create: {
        user_id: userId,
        stripe_customer_id: subscription.customer as string,
        stripe_subscription_id: subscription.id,
        plan_type: price.metadata?.tier || "pro",
        optimizations_limit: parseInt(price.metadata?.optimizations || "500"),
        optimizations_used: 0,
        subscription_status: subscription.status,
        current_period_start: currentPeriodStart,
        current_period_end: currentPeriodEnd,
        created_at: new Date(),
        updated_at: new Date(),
        cancel_at_period_end: subscription.cancel_at_period_end || false,
        cancellation_request_date: subscription.cancel_at_period_end ? new Date() : null,
      },
      update: updateData,
    })

    logger.info("Successfully updated subscription", {
      operation: "subscription_update",
      userId,
      subscriptionId: subscription.id,
      planType: price.metadata?.tier || "pro",
      status: subscription.status,
    })
  } catch (error) {
    logger.errorWithSentry(
      "Error updating subscription:",
      {
        operation: "subscription_update",
        userId,
        subscriptionId: subscription.id,
      },
      {},
      error as Error,
    )
    dependencies.Sentry.captureException(error)
  }
}

export async function handleSubscriptionCancellation(subscription: any) {
  const userId = subscription.metadata.userId
  if (!userId) return

  try {
    await dependencies.prisma.subscriptions.update({
      where: { user_id: userId },
      data: {
        subscription_status: "canceled",
        plan_type: "free",
        optimizations_limit: SUBSCRIPTION_PLANS.FREE.optimizations,
        stripe_subscription_id: null,
        cancel_at_period_end: false,
        cancellation_request_date: null,
      },
    })

    logger.info("Cancelled subscription", {
      operation: "subscription_cancellation",
      userId,
      subscriptionId: subscription.id,
    })
  } catch (error) {
    logger.errorWithSentry(
      "Error cancelling subscription:",
      {
        operation: "subscription_cancellation",
        userId,
        subscriptionId: subscription.id,
      },
      {},
      error as Error,
    )
    dependencies.Sentry.captureException(error)
  }
}

export async function handleFailedPayment(invoice: any) {
  if (!invoice.subscription) return

  try {
    const subscription = await dependencies.stripe.subscriptions.retrieve(invoice.subscription)
    const userId = subscription.metadata.userId
    if (!userId) return

    await dependencies.prisma.subscriptions.update({
      where: { user_id: userId },
      data: {
        subscription_status: "past_due",
      },
    })

    logger.info("Updated subscription status to past_due", {
      operation: "failed_payment",
      userId,
      subscriptionId: invoice.subscription,
    })
  } catch (error) {
    logger.errorWithSentry(
      "Error handling failed payment:",
      {
        operation: "failed_payment",
        subscriptionId: invoice.subscription,
      },
      {},
      error as Error,
    )
    dependencies.Sentry.captureException(error)
  }
}
