import { createAdminClient } from '@/shared/db/supabase-client';
import { stripe } from '@/shared/billing/stripe-client';
import type Stripe from 'stripe';

// --- ASA GENERATED START ---
// Webhook event processor — processes pending events from webhook_events table.
// Called by scheduled processor (Supabase Cron or Vercel Cron).
// Implements: idempotency, locking, retry tracking, always-fetch-current-state.
// --- ASA GENERATED END ---

// --- USER CODE START ---

const BATCH_SIZE = 10;
const LOCK_TIMEOUT_MINUTES = 5;

/**
 * Process pending webhook events from the inbox table.
 * Call this from a cron job / scheduled function.
 */
export async function processWebhookEvents(): Promise<{ processed: number; failed: number }> {
  const supabase = createAdminClient();
  let processed = 0;
  let failed = 0;

  // Lock and fetch pending events (skip already locked)
  const cutoff = new Date(Date.now() - LOCK_TIMEOUT_MINUTES * 60 * 1000).toISOString();

  const { data: events } = await supabase
    .from('webhook_events')
    .select('*')
    .eq('status', 'pending')
    .or(`locked_at.is.null,locked_at.lt.${cutoff}`)
    .order('received_at', { ascending: true })
    .limit(BATCH_SIZE);

  if (!events || events.length === 0) return { processed, failed };

  for (const event of events) {
    // Lock the event
    await supabase
      .from('webhook_events')
      .update({ locked_at: new Date().toISOString(), status: 'processing' })
      .eq('id', event.id)
      .eq('status', 'pending');

    try {
      await handleStripeEvent(event.type, event.payload);

      // Mark as processed
      await supabase
        .from('webhook_events')
        .update({
          status: 'processed',
          processed_at: new Date().toISOString(),
          attempt_count: event.attempt_count + 1,
          locked_at: null,
        })
        .eq('id', event.id);

      processed++;
    } catch (err) {
      // Mark as failed with error
      await supabase
        .from('webhook_events')
        .update({
          status: event.attempt_count >= 2 ? 'failed' : 'pending',
          last_error: err instanceof Error ? err.message : String(err),
          attempt_count: event.attempt_count + 1,
          locked_at: null,
        })
        .eq('id', event.id);

      failed++;
    }
  }

  return { processed, failed };
}

/**
 * Handle a single Stripe event.
 * Always fetches current state from Stripe — never trusts webhook payload alone.
 */
async function handleStripeEvent(type: string, payload: Record<string, unknown>): Promise<void> {
  const supabase = createAdminClient();
  const object = payload.object as Record<string, unknown>;

  switch (type) {
    case 'customer.subscription.created':
    case 'customer.subscription.updated': {
      // Always fetch current subscription from Stripe
      const sub = await stripe.subscriptions.retrieve(object.id as string);
      await syncSubscription(supabase, sub);
      break;
    }

    case 'customer.subscription.deleted': {
      const sub = payload.object as unknown as Stripe.Subscription;
      const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer?.id;

      await supabase
        .from('subscriptions')
        .update({ plan: 'free', status: 'canceled', stripe_subscription_id: null })
        .eq('stripe_customer_id', customerId);
      break;
    }

    case 'invoice.payment_failed': {
      const invoice = payload.object as unknown as Stripe.Invoice;
      const customerId = typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id;

      await supabase
        .from('subscriptions')
        .update({ status: 'past_due' })
        .eq('stripe_customer_id', customerId);
      break;
    }

    case 'invoice.paid': {
      const invoice = payload.object as unknown as Stripe.Invoice;
      const customerId = typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id;

      // Re-activate if was past_due
      await supabase
        .from('subscriptions')
        .update({ status: 'active' })
        .eq('stripe_customer_id', customerId)
        .eq('status', 'past_due');
      break;
    }

    default:
      // Unhandled event type — log but don't fail
      console.log(`[webhook-processor] Unhandled event type: ${type}`);
  }
}

/**
 * Sync subscription state from Stripe to local DB.
 * Implements "always fetch current state" pattern.
 */
async function syncSubscription(
  supabase: ReturnType<typeof createAdminClient>,
  sub: Stripe.Subscription,
): Promise<void> {
  const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer.id;
  const priceId = sub.items.data[0]?.price?.id;

  // Map Stripe status to our billing state machine
  let status: string;
  switch (sub.status) {
    case 'active': status = 'active'; break;
    case 'trialing': status = 'trialing'; break;
    case 'past_due': status = 'past_due'; break;
    case 'canceled': status = 'canceled'; break;
    case 'unpaid': status = 'past_due'; break;
    default: status = sub.status;
  }

  // Determine plan from price ID
  const { PLANS } = await import('@/shared/billing/plans');
  const plan = PLANS.find((p: { stripe_price_id?: string }) => p.stripe_price_id === priceId)?.id ?? 'pro';

  await supabase
    .from('subscriptions')
    .update({
      plan,
      status,
      stripe_subscription_id: sub.id,
    })
    .eq('stripe_customer_id', customerId);
}

// --- USER CODE END ---
