/**
 * Stripe Subscriptions Module
 *
 * Handles subscription management including:
 * - Creating subscriptions
 * - Upgrading/downgrading plans
 * - Changing billing periods
 * - Proration calculations
 */

import Stripe from 'stripe';
import { stripe } from './client';
import { findPriceForTier } from './pricing';
import { prisma } from '@/lib/db';
import { subscriptionsAdapter } from '@/lib/adapters/subscriptions';
import { logger } from '@/lib/logger';

// =============================================================================
// Type Definitions
// =============================================================================

/**
 * Line item in the legacy upcoming invoice response.
 * The legacy invoices.upcoming() API returns proration directly on line items,
 * unlike the modern InvoiceLineItem which has proration in parent.subscription_item_details.
 */
interface UpcomingInvoiceLineItem {
  id: string;
  amount: number;
  description: string | null;
  proration: boolean;
  period: { start: number; end: number };
}

/**
 * Type for Stripe's upcoming invoice response.
 * The invoices.upcoming() method is a legacy API that returns Invoice-like data.
 * Using createPreview() is preferred but requires different parameter structure.
 */
interface UpcomingInvoiceResponse {
  amount_due: number;
  currency: string;
  next_payment_attempt: number | null;
  period_end: number;
  lines: {
    data: UpcomingInvoiceLineItem[];
  };
}

// =============================================================================
// Subscription CRUD
// =============================================================================

/**
 * Cancel a subscription
 * @param subscriptionId - Stripe subscription ID
 * @returns Cancelled subscription
 */
export async function cancelSubscription(subscriptionId: string): Promise<Stripe.Subscription> {
  try {
    const subscription = await stripe.subscriptions.cancel(subscriptionId);
    return subscription;
  } catch (error) {
    logger.error('Error cancelling subscription', error);
    throw error;
  }
}

/**
 * Update a subscription
 * @param subscriptionId - Stripe subscription ID
 * @param params - Update parameters
 * @returns Updated subscription
 */
export async function updateSubscription(
  subscriptionId: string,
  params: Stripe.SubscriptionUpdateParams
): Promise<Stripe.Subscription> {
  try {
    const subscription = await stripe.subscriptions.update(subscriptionId, params);
    return subscription;
  } catch (error) {
    logger.error('Error updating subscription', error);
    throw error;
  }
}

// =============================================================================
// Subscription Upgrade/Downgrade
// =============================================================================

/**
 * Preview plan change using Stripe's invoices.upcoming API
 * Returns accurate proration amounts directly from Stripe
 */
export async function previewPlanChange(params: {
  subscriptionId: string;
  newTierId: string;
  billingPeriod: 'monthly' | 'yearly';
}): Promise<{
  amountDue: number;
  currency: string;
  creditFromCurrent: number;
  chargeForNew: number;
  netAmount: number;
  periodEnd: Date;
  lineItems: Array<{
    description: string | null;
    amount: number;
    proration: boolean;
  }>;
}> {
  const { prisma } = await import('@/lib/db');
  const stripePricesConfig = (await import('@/lib/config/stripe-prices.json')).default;

  const subscription = await subscriptionsAdapter.findUnique({
    where: { id: params.subscriptionId },
  });

  if (!subscription || !subscription.stripeSubscriptionId || !subscription.stripeCustomerId) {
    throw new Error(`Subscription ${params.subscriptionId} not found or missing Stripe IDs`);
  }

  // Get price ID from stripe-prices.json
  const tierKey = params.newTierId as 'tier1' | 'tier2' | 'tier3' | 'tier4' | 'tier5';
  const tierPrices = stripePricesConfig.tiers[tierKey];
  if (!tierPrices) {
    throw new Error(`Invalid tier: ${params.newTierId}`);
  }
  const priceId = params.billingPeriod === 'monthly' ? tierPrices.monthly : tierPrices.yearly;

  // Get Stripe subscription to find item ID
  const stripeSubscription = await stripe.subscriptions.retrieve(subscription.stripeSubscriptionId);
  const subscriptionItemId = stripeSubscription.items.data[0]?.id;

  if (!subscriptionItemId) {
    throw new Error('No subscription item found in Stripe subscription');
  }

  // Call Stripe invoices.upcoming for accurate proration
  // Note: invoices.upcoming() is a legacy method not in TypeScript types.
  // The modern alternative is invoices.createPreview() but requires different params.
  const upcomingInvoice = await (stripe.invoices as unknown as {
    upcoming: (params: {
      customer: string;
      subscription: string;
      subscription_items: Array<{ id: string; price: string }>;
    }) => Promise<UpcomingInvoiceResponse>;
  }).upcoming({
    customer: subscription.stripeCustomerId,
    subscription: subscription.stripeSubscriptionId,
    subscription_items: [
      {
        id: subscriptionItemId,
        price: priceId,
      },
    ],
  });

  // Parse line items to extract credit/charge breakdown
  let creditFromCurrent = 0;
  let chargeForNew = 0;

  for (const line of upcomingInvoice.lines.data) {
    if (line.proration) {
      if (line.amount < 0) {
        creditFromCurrent += Math.abs(line.amount);
      } else {
        chargeForNew += line.amount;
      }
    }
  }

  return {
    amountDue: upcomingInvoice.amount_due,
    currency: upcomingInvoice.currency,
    creditFromCurrent,
    chargeForNew,
    netAmount: upcomingInvoice.amount_due,
    periodEnd: new Date((upcomingInvoice.period_end || 0) * 1000),
    lineItems: upcomingInvoice.lines.data.map((line: UpcomingInvoiceLineItem) => ({
      description: line.description,
      amount: line.amount,
      proration: line.proration || false,
    })),
  };
}

/**
 * Preview subscription upgrade
 */
export async function previewSubscriptionUpgrade(params: {
  subscriptionId: string;
  newTierId: string;
  currency: string;
  billingPeriod: 'monthly' | 'yearly';
}): Promise<{
  amountDue: number;
  currency: string | null;
  nextPaymentAttempt: number | null;
  periodEnd: number;
  lineItems: Array<{
    id: string;
    description: string | null;
    amount: number;
    proration: boolean;
    period: { start: number; end: number };
  }>;
}> {
  const { subscriptionId, newTierId, currency, billingPeriod } = params;

  try {
    // prisma is imported at top of file

    const currentSubscription = await subscriptionsAdapter.findUnique({
      where: { id: subscriptionId },
    });

    if (!currentSubscription) {
      throw new Error(`Subscription ${subscriptionId} not found`);
    }

    const priceId = await findPriceForTier({
      tierId: newTierId,
      currency,
      billingPeriod,
    });

    const fullSubscription = await stripe.subscriptions.retrieve(
      currentSubscription.stripeSubscriptionId
    );
    const subscriptionItemId = fullSubscription.items.data[0]?.id;

    if (!subscriptionItemId) {
      throw new Error('Subscription item not found for upgrade preview');
    }

    // Note: invoices.upcoming() is a legacy method not in TypeScript types.
    // The modern alternative is invoices.createPreview() but requires different params.
    const upcomingInvoice = await (stripe.invoices as unknown as {
      upcoming: (params: {
        customer: string;
        subscription: string;
        subscription_items: Array<{ id: string; price: string }>;
        expand?: string[];
      }) => Promise<UpcomingInvoiceResponse>;
    }).upcoming({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      subscription_items: [
        {
          id: subscriptionItemId,
          price: priceId,
        },
      ],
      expand: ['lines.data.price.product'],
    });

    return {
      amountDue: upcomingInvoice.amount_due,
      currency: upcomingInvoice.currency,
      nextPaymentAttempt: upcomingInvoice.next_payment_attempt,
      periodEnd: upcomingInvoice.period_end,
      lineItems: upcomingInvoice.lines.data.map((line: UpcomingInvoiceLineItem) => ({
        id: line.id,
        description: line.description,
        amount: line.amount,
        proration: line.proration,
        period: line.period,
      })),
    };
  } catch (error) {
    logger.error('Error previewing subscription upgrade', error);
    throw error;
  }
}

/**
 * Upgrade a subscription to a higher tier
 */
export async function upgradeSubscription(params: {
  subscriptionId: string;
  newTierId: string;
  currency: string;
  billingPeriod: 'monthly' | 'yearly';
  resetTrial?: boolean;
  createImmediateInvoice?: boolean;
  language?: string;
}): Promise<{
  prorationAmount: number;
  newTierId: string;
  trialEndsAt?: Date;
  invoiceId?: string;
  metadata?: Record<string, unknown>;
}> {
  const {
    subscriptionId,
    newTierId,
    currency,
    billingPeriod,
    resetTrial,
    language,
  } = params;

  try {
    // prisma is imported at top of file
    const { createId } = await import('@paralleldrive/cuid2');

    // 1. Get current subscription from database
    const currentSubscription = await subscriptionsAdapter.findUnique({
      where: { id: subscriptionId },
    });

    if (!currentSubscription) {
      throw new Error(`Subscription ${subscriptionId} not found`);
    }

    // 2. Find price for new tier
    const newPriceId = await findPriceForTier({
      tierId: newTierId,
      currency,
      billingPeriod,
    });

    // 3. Retrieve full subscription to get subscription item ID
    const fullSubscription = await stripe.subscriptions.retrieve(
      currentSubscription.stripeSubscriptionId
    );
    const subscriptionItemId = fullSubscription.items.data[0].id;

    // 4. Calculate proration using Stripe invoice preview
    // Note: invoices.upcoming() is a legacy method not in TypeScript types.
    const upcomingInvoice = await (stripe.invoices as unknown as {
      upcoming: (params: {
        customer: string;
        subscription: string;
        subscription_items: Array<{ id: string; price: string }>;
      }) => Promise<UpcomingInvoiceResponse>;
    }).upcoming({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      subscription_items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
    });

    const prorationAmount = upcomingInvoice.amount_due;

    // 5. Update Stripe subscription
    const updateParams: Stripe.SubscriptionUpdateParams = {
      items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
      proration_behavior: 'create_prorations',
      metadata: {
        ...fullSubscription.metadata,
        tier_id: newTierId,
        billing_period: billingPeriod,
      },
    };

    if (language) {
      updateParams.metadata = { ...updateParams.metadata, language };
    }

    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 14);
      updateParams.trial_end = Math.floor(trialEndDate.getTime() / 1000);
    }

    const updatedSubscription = await stripe.subscriptions.update(
      currentSubscription.stripeSubscriptionId,
      updateParams
    );

    // 6. Create immediate invoice if subscription is not in trial
    let immediateInvoice = null;
    if (currentSubscription.status !== 'trialing' && currentSubscription.trialEndsAt === null) {
      immediateInvoice = await stripe.invoices.create({
        customer: currentSubscription.stripeCustomerId,
        subscription: currentSubscription.stripeSubscriptionId,
        auto_advance: true,
      });
    }

    // 7. Update database subscription record
    const dbUpdateData: Record<string, unknown> = {
      tierId: newTierId,
      stripePriceId: newPriceId,
    };

    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 14);
      dbUpdateData.trialEndsAt = trialEndDate;
    }

    const updatedDbSubscription = await subscriptionsAdapter.update({
      where: { id: subscriptionId },
      data: dbUpdateData,
    });

    // Subscription already updated via subscriptionsAdapter above

    // 8. Record billing history event for upgrade
    const upgradeHistoryId = createId();
    await prisma.billing_history.create({
      data: {
        id: upgradeHistoryId,
        customerId: currentSubscription.stripeCustomerId,
        subscriptionId: subscriptionId,
        event: 'subscription_upgraded',
        amount: prorationAmount,
        currency: currency,
        metadata: {
          from_tier: currentSubscription.tierId,
          to_tier: newTierId,
          proration_amount: prorationAmount,
        },
      },
    });

    // Billing history already recorded via prisma.billing_history.create above

    // 9. Record billing history event for immediate invoice if created
    if (immediateInvoice) {
      const invoiceHistoryId = createId();
      await prisma.billing_history.create({
        data: {
          id: invoiceHistoryId,
          customerId: currentSubscription.stripeCustomerId,
          subscriptionId: subscriptionId,
          invoiceId: immediateInvoice.id,
          event: 'upgrade_invoice_created',
          amount: immediateInvoice.amount_due,
          currency: currency,
          metadata: {
            from_tier: currentSubscription.tierId,
            to_tier: newTierId,
            proration_amount: immediateInvoice.amount_due,
          },
        },
      });

      // Billing history already recorded via prisma.billing_history.create above
    }

    const result: {
      prorationAmount: number;
      newTierId: string;
      trialEndsAt?: Date;
      invoiceId?: string;
      metadata?: Record<string, unknown>;
    } = {
      prorationAmount,
      newTierId,
    };

    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 14);
      result.trialEndsAt = trialEndDate;
    }

    if (immediateInvoice) {
      result.invoiceId = immediateInvoice.id;
    }

    if (updatedSubscription.metadata) {
      result.metadata = updatedSubscription.metadata as Record<string, unknown>;
    }

    return result;
  } catch (error) {
    logger.error('Error upgrading subscription', error);
    throw error;
  }
}

/**
 * Downgrade a subscription to a lower tier
 */
export async function downgradeSubscription(params: {
  subscriptionId: string;
  newTierId: string;
  currency: string;
  billingPeriod: 'monthly' | 'yearly';
}): Promise<{
  prorationCredit: number;
  newTierId: string;
}> {
  const { subscriptionId, newTierId, currency, billingPeriod } = params;

  try {
    // prisma is imported at top of file

    const currentSubscription = await subscriptionsAdapter.findUnique({
      where: { id: subscriptionId },
    });

    if (!currentSubscription) {
      throw new Error(`Subscription ${subscriptionId} not found`);
    }

    const newPriceId = await findPriceForTier({
      tierId: newTierId,
      currency,
      billingPeriod,
    });

    const fullSubscription = await stripe.subscriptions.retrieve(
      currentSubscription.stripeSubscriptionId
    );
    const subscriptionItemId = fullSubscription.items.data[0].id;

    // Note: invoices.upcoming() is a legacy method not in TypeScript types.
    const upcomingInvoice = await (stripe.invoices as unknown as {
      upcoming: (params: {
        customer: string;
        subscription: string;
        subscription_items: Array<{ id: string; price: string }>;
      }) => Promise<UpcomingInvoiceResponse>;
    }).upcoming({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      subscription_items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
    });

    const prorationCredit = upcomingInvoice.amount_due;

    await stripe.subscriptions.update(currentSubscription.stripeSubscriptionId, {
      items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
      proration_behavior: 'create_prorations',
      metadata: {
        tier_id: newTierId,
        billing_period: billingPeriod,
      },
    });

    await subscriptionsAdapter.update({
      where: { id: subscriptionId },
      data: {
        tierId: newTierId,
        stripePriceId: newPriceId,
      },
    });

    // Note: billing_history model exists in schema but Prisma client types are not fully generated
    // This is a known Prisma client generation issue - the model works at runtime
    const { createId } = await import('@paralleldrive/cuid2');
    const historyId = createId();
    await prisma.billing_history.create({
      data: {
        id: historyId,
        customerId: currentSubscription.stripeCustomerId,
        subscriptionId: subscriptionId,
        event: 'subscription_downgraded',
        amount: prorationCredit,
        currency: currency,
        metadata: {
          from_tier: currentSubscription.tierId,
          to_tier: newTierId,
          proration_credit: prorationCredit,
        },
      },
    });

    return {
      prorationCredit,
      newTierId,
    };
  } catch (error) {
    logger.error('Error downgrading subscription', error);
    throw error;
  }
}

/**
 * Change subscription billing period (monthly ↔ yearly)
 */
export async function changeBillingPeriod(params: {
  subscriptionId: string;
  newBillingPeriod: 'monthly' | 'yearly';
}): Promise<{
  billingPeriod: 'monthly' | 'yearly';
}> {
  const { subscriptionId, newBillingPeriod } = params;

  try {
    // prisma is imported at top of file

    const currentSubscription = await subscriptionsAdapter.findUnique({
      where: { id: subscriptionId },
    });

    if (!currentSubscription) {
      throw new Error(`Subscription ${subscriptionId} not found`);
    }

    const newPriceId = await findPriceForTier({
      tierId: currentSubscription.tierId,
      currency: 'gbp',
      billingPeriod: newBillingPeriod,
    });

    const fullSubscription = await stripe.subscriptions.retrieve(
      currentSubscription.stripeSubscriptionId
    );
    const subscriptionItemId = fullSubscription.items.data[0].id;

    await stripe.subscriptions.update(currentSubscription.stripeSubscriptionId, {
      items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
      proration_behavior: 'create_prorations',
      metadata: {
        tier_id: currentSubscription.tierId,
        billing_period: newBillingPeriod,
      },
    });

    await subscriptionsAdapter.update({
      where: { id: subscriptionId },
      data: {
        billingPeriod: newBillingPeriod,
        stripePriceId: newPriceId,
      },
    });

    // Note: billing_history model exists in schema but Prisma client types are not fully generated
    const { createId } = await import('@paralleldrive/cuid2');
    const historyId = createId();
    await prisma.billing_history.create({
      data: {
        id: historyId,
        customerId: currentSubscription.stripeCustomerId,
        subscriptionId: subscriptionId,
        event: 'billing_period_changed',
        amount: 0,
        currency: 'gbp',
        metadata: {
          from_period: currentSubscription.billingPeriod,
          to_period: newBillingPeriod,
        },
      },
    });

    return {
      billingPeriod: newBillingPeriod,
    };
  } catch (error) {
    logger.error('Error changing billing period', error);
    throw error;
  }
}
