import Stripe from 'stripe'
import type { InsertSubscription } from '../../database/queries/subscriptions.ts'
import {
  createOrUpdateStripeProduct,
  createOrUpdateStripePrice
} from '../../database/queries/stripe.ts'
import { getCustomerById } from '../../database/queries/customers.ts'
import { upsertSubscription } from '../../database/queries/subscriptions.ts'
import { updateUser } from '../../database/queries/users'
import { stripeService } from '../../services/stripe.ts'

interface StripePeriodFields {
  current_period_end?: number
  current_period_start?: number
}

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const webhookSecret = config.stripeWebhookSecret
  const stripeSecretKey = config.stripeSecretKey

  if (!webhookSecret || !stripeSecretKey) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Stripe configuration missing'
    })
  }

  const stripe = new Stripe(stripeSecretKey, {
    apiVersion: '2025-11-17.clover'
  })

  const body = await readRawBody(event)
  const stripeSignature = getHeader(event, 'stripe-signature')

  if (!stripeSignature) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Stripe signature missing'
    })
  }
  if (!body) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Request body missing'
    })
  }
  let stripeEvent: Stripe.Event
  try {
    stripeEvent = await stripe.webhooks.constructEvent(
      body,
      stripeSignature,
      webhookSecret
    )
  } catch (err) {
    console.error('[Stripe Webhook] Error constructing event:', err)
    throw createError({
      statusCode: 400,
      statusMessage: 'Webhook signature verification failed',
      message: err instanceof Error ? err.message : 'Unknown error'
    })
  }
  const type = stripeEvent.type
  console.log('[Stripe Webhook] Event received', { id: stripeEvent.id, type })

  const relevantEvents = [
    'checkout.session.completed',
    'checkout.session.async_payment_succeeded',
    'customer.subscription.created',
    'customer.subscription.updated',
    'customer.subscription.deleted',
    'customer.subscription.paused',
    'customer.subscription.resumed',
    'price.created',
    'price.deleted',
    'price.updated',
    'product.created',
    'product.deleted',
    'product.updated'
  ]
  if (!relevantEvents.includes(type)) {
    console.log('[Stripe Webhook] Not a relevant event:', type)
    return 'OK'
  }
  const data = stripeEvent.data.object
  if (!data) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Invalid event data'
    })
  }
  console.log('Received event', type)
  try {
    switch (type) {
      case 'product.created':
      case 'product.updated': {
        const product = data as Stripe.Product
        console.log('[Stripe Webhook] Handling product event', { id: product.id, type })
        await handleProductEvent(product)
        console.log('[Stripe Webhook] Product event handled', { id: product.id })
        break
      }
      case 'price.created':
      case 'price.updated': {
        const price = data as Stripe.Price
        console.log('[Stripe Webhook] Handling price event', { id: price.id, type })
        await handlePriceEvent(price)
        console.log('[Stripe Webhook] Price event handled', { id: price.id })
        break
      }
      case 'customer.subscription.created':
      case 'customer.subscription.updated':
      case 'customer.subscription.deleted':
      case 'customer.subscription.resumed':
      case 'customer.subscription.paused': {
        const subscription = data as Stripe.Subscription
        console.log('[Stripe Webhook] Handling subscription event', { id: subscription.id, type })
        await handleSubscriptionEvent(stripe, subscription)
        console.log('[Stripe Webhook] Subscription event handled', { id: subscription.id })
        break
      }
      case 'checkout.session.completed':
      case 'checkout.session.async_payment_succeeded': {
        const session = data as Stripe.Checkout.Session
        console.log('[Stripe Webhook] Handling checkout session event', { id: session.id, type })
        await handleCheckoutSessionEvent(session)
        console.log('[Stripe Webhook] Checkout session handled', { id: session.id })
        break
      }
      default:
        throw createError({
          statusCode: 400,
          statusMessage: 'Unhandled event type.'
        })
    }
  } catch (err) {
    console.log(err)
    return 'Error'
  }
  return 'OK'
})

const handleProductEvent = async (data: Stripe.Product) => {
  console.log('[Stripe Webhook] Upserting product in DB', { id: data.id, name: data.name })
  const product = await createOrUpdateStripeProduct({
    id: data.id,
    name: data.name,
    description: data.description,
    active: data.active,
    metadata: data.metadata,
    features: (data.marketing_features || [])
      .filter(f => !!f.name)
      .map(f => ({ name: f.name as string }))
  })
  console.log('[Stripe Webhook] Product upserted', { id: data.id })
  return product
}

const handlePriceEvent = async (data: Stripe.Price) => {
  console.log('[Stripe Webhook] Upserting price in DB', { id: data.id, product: data.product })
  const productId = typeof data.product === 'string' ? data.product : data.product?.id
  if (!productId) {
    throw createError({ statusCode: 400, statusMessage: 'Price product missing' })
  }
  const record = {
    id: data.id,
    type: data.type,
    currency: data.currency,
    unitAmount: data.unit_amount ?? 0,
    interval: data.recurring?.interval ?? 'month',
    intervalCount: data.recurring?.interval_count ?? 1,
    productId,
    description: data.nickname,
    active: data.active,
    metadata: data.metadata,
    trialPeriodDays: data.recurring?.trial_period_days
  }
  await createOrUpdateStripePrice(record)
  console.log('[Stripe Webhook] Price upserted', { id: data.id })
  return record
}

const handleSubscriptionEvent = async (stripe: Stripe, data: Stripe.Subscription) => {
  const customer = await getCustomerById(data.customer as string)
  if (!customer) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Customer not found'
    })
  }
  const userId = customer.userId
  if (!userId) {
    throw createError({
      statusCode: 400,
      statusMessage: 'User not found'
    })
  }

  // Safely read first item and price
  const firstItem = data.items?.data?.[0]
  if (!firstItem?.price?.id) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Subscription item price missing'
    })
  }

  // Ensure price (and product) exist locally to satisfy FK constraints
  try {
    console.log('[Stripe Webhook] Ensuring local product/price exist', { priceId: firstItem.price.id })
    const price = await stripe.prices.retrieve(firstItem.price.id, { expand: ['product'] })
    const product = typeof price.product === 'string' ? null : (price.product as Stripe.Product)
    if (product && !('deleted' in product && product.deleted)) {
      console.log('[Stripe Webhook] Upserting product before subscription upsert', { id: product.id })
      await createOrUpdateStripeProduct({
        id: product.id,
        name: product.name,
        description: product.description,
        active: product.active,
        metadata: product.metadata as unknown as Record<string, unknown> | null,
        features: (product as Stripe.Product & { marketing_features?: Array<{ name?: string }> })
          .marketing_features
          ? (product as Stripe.Product & { marketing_features?: Array<{ name?: string }> })
              .marketing_features!
              .filter(f => !!f?.name)
              .map(f => ({ name: f!.name as string }))
          : null
      })
      console.log('[Stripe Webhook] Product upsert ensured', { id: product.id })
    }

    console.log('[Stripe Webhook] Upserting price before subscription upsert', { id: price.id })
    await createOrUpdateStripePrice({
      id: price.id,
      description: price.nickname ?? null,
      currency: price.currency,
      unitAmount: price.unit_amount ?? 0,
      type: price.type,
      interval: price.recurring?.interval ?? 'month',
      intervalCount: price.recurring?.interval_count ?? 1,
      trialPeriodDays: price.recurring?.trial_period_days ?? null,
      active: price.active,
      metadata: price.metadata as unknown as Record<string, unknown> | null,
      productId: typeof price.product === 'string' ? price.product : price.product.id
    })
    console.log('[Stripe Webhook] Price upsert ensured', { id: price.id })
  } catch (err) {
    console.error('[Webhook] Failed to ensure local price/product', err)
  }

  const dataPeriod = data as unknown as StripePeriodFields
  console.log('[Stripe Webhook] Upserting subscription in DB', {
    id: data.id,
    customerId: data.customer,
    userId,
    priceId: firstItem.price.id,
    status: data.status
  })
  const subscription = await upsertSubscription({
    id: data.id,
    customerId: data.customer as string,
    priceId: firstItem.price.id,
    userId: userId,
    status: data.status,
    metadata: data.metadata,
    quantity: firstItem.quantity ?? 1,
    cancelAtPeriodEnd: data.cancel_at_period_end,
    currentPeriodEnd: new Date((dataPeriod.current_period_end ?? 0) * 1000),
    currentPeriodStart: new Date((dataPeriod.current_period_start ?? 0) * 1000),
    endedAt: data.ended_at ? new Date(data.ended_at * 1000) : null,
    cancelAt: data.cancel_at ? new Date(data.cancel_at * 1000) : null,
    trialStart: data.trial_start ? new Date(data.trial_start * 1000) : null,
    trialEnd: data.trial_end ? new Date(data.trial_end * 1000) : null
  })

  // Update user's pro account status based on subscription status
  const isProAccount = ['active', 'trialing'].includes(data.status)
  console.log('[Stripe Webhook] Updating user proAccount', { userId, isProAccount })
  await updateUser(userId, {
    proAccount: isProAccount
  })

  console.log('[Stripe Webhook] Subscription upserted', { id: subscription.id, status: subscription.status })
  return subscription
}

const handleCheckoutSessionEvent = async (data: Stripe.Checkout.Session) => {
  console.log('handleCheckoutSessionEvent', data)
  if (data.mode !== 'subscription') {
    return // Only handle subscription checkouts
  }

  const existingCustomer = await getCustomerById(data.customer as string)
  if (!existingCustomer) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Customer not found'
    })
  }
  const customerId = existingCustomer.id
  const userId = existingCustomer.userId

  if (!userId) {
    throw createError({
      statusCode: 400,
      statusMessage: 'User not found'
    })
  }

  const session = await stripeService.getCheckoutSession(data.id)

  if (session.status === 'complete') {
    // Get the subscription from the session
    const subscriptionId = session.subscription as string
    console.log('[Stripe Webhook] Fetching subscription from session', { subscriptionId })
    const subscription = await stripeService.getSubscription(subscriptionId)

    const firstItem = subscription.items?.data?.[0]
    if (!firstItem?.price?.id) {
      throw createError({ statusCode: 400, statusMessage: 'Subscription item price missing' })
    }

    const subPeriod = subscription as unknown as StripePeriodFields
    const subscriptionData: InsertSubscription = {
      id: subscription.id,
      customerId,
      userId,
      priceId: firstItem.price.id,
      status: subscription.status,
      quantity: firstItem.quantity ?? 1,
      cancelAtPeriodEnd: subscription.cancel_at_period_end,
      currentPeriodEnd: new Date((subPeriod.current_period_end ?? 0) * 1000),
      currentPeriodStart: new Date((subPeriod.current_period_start ?? 0) * 1000),
      cancelAt: subscription.cancel_at
        ? new Date(subscription.cancel_at * 1000)
        : null,
      endedAt: subscription.ended_at
        ? new Date(subscription.ended_at * 1000)
        : null,
      trialStart: subscription.trial_start
        ? new Date(subscription.trial_start * 1000)
        : null,
      trialEnd: subscription.trial_end
        ? new Date(subscription.trial_end * 1000)
        : null,
      metadata: subscription.metadata
    }

    // Upsert the subscription to ensure we have the latest data
    console.log('[Stripe Webhook] Upserting subscription from checkout session', { id: subscriptionData.id })
    await upsertSubscription(subscriptionData)

    // Update user's pro account status
    console.log('[Stripe Webhook] Updating user proAccount from checkout session', { userId, isActive: subscription.status === 'active' })
    await updateUser(userId, {
      proAccount: subscription.status === 'active'
    })
  }
}
