import Stripe from 'stripe';
import { prisma } from '@/lib/db';
import { decrypt } from '@/lib/encryption';
import { subscriptionsAdapter } from '@/lib/adapters/subscriptions';
import { billingHistory } from '@/lib/adapters/billing';

// Stripe public key for client-side
export const stripePublicKey = process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY;

// Cache for organization-specific Stripe clients
const orgStripeClients = new Map<string, { client: Stripe; createdAt: number }>();
const STRIPE_CLIENT_TTL_MS = 5 * 60 * 1000; // 5 minutes

// Initialize Stripe client (lazy initialization to allow build without env vars)
let stripeInstance: Stripe | null = null;

export const getStripeClient = (): Stripe => {
  if (!stripeInstance) {
    const secretKey = process.env.STRIPE_SECRET_KEY;

    if (!secretKey) {
      throw new Error('STRIPE_SECRET_KEY is not set in environment variables');
    }

    stripeInstance = new Stripe(secretKey, {
      apiVersion: '2025-09-30.clover',
      typescript: true,
    });
  }

  return stripeInstance;
};

// Export stripe instance that initializes on first use
export const stripe = new Proxy({} as Stripe, {
  get: (target, prop) => {
    const client = getStripeClient();
    return client[prop as keyof Stripe];
  },
});

if (!stripePublicKey && process.env.NODE_ENV === 'production') {
  console.warn('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is not set - Stripe checkout will not work');
}

/**
 * Create a checkout session for payment
 * @param params Checkout session parameters
 * @returns Stripe checkout session
 */
export async function createCheckoutSession(
  params: Stripe.Checkout.SessionCreateParams
): Promise<Stripe.Checkout.Session> {
  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/cancelled`,
      ...params,
    });
    return session;
  } catch (error) {
    console.error('Error creating checkout session:', error);
    throw error;
  }
}

/**
 * Create a subscription checkout session
 * @param params Subscription checkout parameters
 * @returns Stripe checkout session
 */
export async function createSubscriptionSession(
  params: Omit<Stripe.Checkout.SessionCreateParams, 'mode'>
): Promise<Stripe.Checkout.Session> {
  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'subscription',
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/subscription/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/subscription/cancelled`,
      ...params,
    });
    return session;
  } catch (error) {
    console.error('Error creating subscription session:', error);
    throw error;
  }
}

/**
 * Retrieve a checkout session
 * @param sessionId Checkout session ID
 * @returns Stripe checkout session
 */
export async function getCheckoutSession(
  sessionId: string
): Promise<Stripe.Checkout.Session | null> {
  try {
    const session = await stripe.checkout.sessions.retrieve(sessionId);
    return session;
  } catch (error) {
    console.error('Error retrieving checkout session:', error);
    return null;
  }
}

/**
 * Get a Stripe client for a specific organization
 * Uses company-specific keys if configured, falls back to platform keys
 *
 * @param organizationId - The organization ID to get client for
 * @returns Object with client, whether it's using company keys, and mode
 */
export async function getStripeClientForOrganization(organizationId: string): Promise<{
  client: Stripe;
  isCompanyKeys: boolean;
  mode: 'test' | 'live' | 'platform';
  publishableKey: string | null;
}> {
  // Check cache first
  const cached = orgStripeClients.get(organizationId);
  if (cached && Date.now() - cached.createdAt < STRIPE_CLIENT_TTL_MS) {
    // Get mode from org for cached client
    const org = await prisma.organizations.findUnique({
      where: { id: organizationId },
      select: { stripeKeysMode: true, stripePublishableKey: true },
    });
    return {
      client: cached.client,
      isCompanyKeys: true,
      mode: (org?.stripeKeysMode as 'test' | 'live') || 'test',
      publishableKey: org?.stripePublishableKey || null,
    };
  }

  // Fetch organization's Stripe keys
  const organization = await prisma.organizations.findUnique({
    where: { id: organizationId },
    select: {
      stripeSecretKeyEncrypted: true,
      stripePublishableKey: true,
      stripeKeysMode: true,
    },
  });

  // If organization has configured keys, use them
  if (organization?.stripeSecretKeyEncrypted) {
    try {
      const secretKey = decrypt(organization.stripeSecretKeyEncrypted);
      const client = new Stripe(secretKey, {
        apiVersion: '2025-09-30.clover',
        typescript: true,
      });

      // Cache the client
      orgStripeClients.set(organizationId, {
        client,
        createdAt: Date.now(),
      });

      return {
        client,
        isCompanyKeys: true,
        mode: (organization.stripeKeysMode as 'test' | 'live') || 'test',
        publishableKey: organization.stripePublishableKey,
      };
    } catch (error) {
      console.error('Failed to decrypt organization Stripe keys:', error);
      // Fall through to platform keys
    }
  }

  // Fall back to platform keys
  return {
    client: getStripeClient(),
    isCompanyKeys: false,
    mode: 'platform',
    publishableKey: stripePublicKey || null,
  };
}

/**
 * Check if organization has Stripe keys configured
 * @param organizationId - The organization ID to check
 * @returns true if keys are configured, false otherwise
 */
export async function hasOrganizationStripeKeys(organizationId: string): Promise<boolean> {
  const organization = await prisma.organizations.findUnique({
    where: { id: organizationId },
    select: { stripeSecretKeyEncrypted: true },
  });
  return !!organization?.stripeSecretKeyEncrypted;
}

/**
 * Clear cached Stripe client for an organization
 * Call this when keys are updated or deleted
 * @param organizationId - The organization ID to clear cache for
 */
export function clearOrgStripeClientCache(organizationId: string): void {
  orgStripeClients.delete(organizationId);
}

/**
 * Create a checkout session using organization-specific Stripe client if available
 * @param organizationId - The organization ID (optional)
 * @param params - Checkout session parameters
 * @returns Stripe checkout session and metadata about which keys were used
 */
export async function createCheckoutSessionForOrganization(
  organizationId: string | null,
  params: Stripe.Checkout.SessionCreateParams
): Promise<{
  session: Stripe.Checkout.Session;
  isCompanyKeys: boolean;
  mode: 'test' | 'live' | 'platform';
}> {
  let stripeClient: Stripe;
  let isCompanyKeys = false;
  let mode: 'test' | 'live' | 'platform' = 'platform';

  if (organizationId) {
    const result = await getStripeClientForOrganization(organizationId);
    stripeClient = result.client;
    isCompanyKeys = result.isCompanyKeys;
    mode = result.mode;
  } else {
    stripeClient = getStripeClient();
  }

  const session = await stripeClient.checkout.sessions.create({
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/cancelled`,
    ...params,
  });

  return { session, isCompanyKeys, mode };
}

/**
 * Create a customer
 * @param params Customer creation parameters
 * @returns Stripe customer
 */
export async function createCustomer(
  params: Stripe.CustomerCreateParams
): Promise<Stripe.Customer> {
  try {
    const customer = await stripe.customers.create(params);
    return customer;
  } catch (error) {
    console.error('Error creating customer:', error);
    throw error;
  }
}

/**
 * Retrieve a customer by ID
 * @param customerId Customer ID
 * @returns Stripe customer or null
 */
export async function getCustomer(
  customerId: string
): Promise<Stripe.Customer | null> {
  try {
    const customer = await stripe.customers.retrieve(customerId);
    return customer as Stripe.Customer;
  } catch (error) {
    console.error('Error retrieving customer:', error);
    return null;
  }
}

/**
 * Create a payment intent
 * @param params Payment intent parameters
 * @returns Stripe payment intent
 */
export async function createPaymentIntent(
  params: Stripe.PaymentIntentCreateParams
): Promise<Stripe.PaymentIntent> {
  try {
    const paymentIntent = await stripe.paymentIntents.create(params);
    return paymentIntent;
  } catch (error) {
    console.error('Error creating payment intent:', error);
    throw error;
  }
}

/**
 * Cancel a subscription
 * @param subscriptionId 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) {
    console.error('Error cancelling subscription:', error);
    throw error;
  }
}

/**
 * Update a subscription
 * @param subscriptionId 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) {
    console.error('Error updating subscription:', error);
    throw error;
  }
}

/**
 * Create a subscription with setup fee
 * Based on pricing table requirements for tier signup flow
 */
export async function createSubscriptionWithSetup(params: {
  customerId: string;
  priceId: string;
  setupFeeAmount: number;
  currency: string;
  metadata?: Record<string, string>;
}): Promise<Stripe.Checkout.Session> {
  const { customerId, priceId, setupFeeAmount, currency, metadata } = params;

  try {
    // Create setup fee as a one-time price
    const setupPrice = await stripe.prices.create({
      currency: currency.toLowerCase(),
      unit_amount: setupFeeAmount * 100, // Convert to cents
      product_data: {
        name: 'One-time Setup Fee',
      },
    });

    const session = await stripe.checkout.sessions.create({
      mode: 'subscription',
      customer: customerId,
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
        {
          price: setupPrice.id,
          quantity: 1,
        },
      ],
      subscription_data: {
        metadata: {
          ...metadata,
          has_setup_fee: 'true',
          setup_fee_amount: setupFeeAmount.toString(),
        },
        trial_period_days: 7,
      },
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/${metadata?.language || 'en'}/signup/verify?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/${metadata?.language || 'en'}/signup?cancelled=true`,
    });

    return session;
  } catch (error) {
    console.error('Error creating subscription with setup:', error);
    throw error;
  }
}

/**
 * Get subscription price based on tier, currency, and billing period
 * Matches pricing table logic from pricing/page.tsx
 */
export async function getPriceForTier(
  tierId: string,
  currency: string,
  billingPeriod: 'monthly' | 'yearly'
): Promise<Stripe.Price | null> {
  try {
    const prices = await stripe.prices.list({
      limit: 100,
      expand: ['data.product'],
    });

    const matchingPrice = prices.data.find((price) => {
      const product = price.product as Stripe.Product;
      return (
        product.metadata?.tier_id === tierId &&
        price.currency === currency.toLowerCase() &&
        price.recurring?.interval === (billingPeriod === 'monthly' ? 'month' : 'year')
      );
    });

    return matchingPrice || null;
  } catch (error) {
    console.error('Error finding price for tier:', error);
    return null;
  }
}

/**
 * Find price ID for tier, currency, and billing period
 * Used by subscription upgrade/downgrade flows
 */
export async function findPriceForTier(params: {
  tierId: string;
  currency: string;
  billingPeriod: 'monthly' | 'yearly';
}): Promise<string> {
  const { tierId, currency, billingPeriod } = params;

  const price = await getPriceForTier(tierId, currency, billingPeriod);

  if (!price) {
    throw new Error(`Price not found for ${tierId} with currency ${currency} and billing period ${billingPeriod}`);
  }

  return price.id;
}

/**
 * Calculate subscription cost with 7% yearly discount
 * Matches pricing calculation from pricing/page.tsx
 */
export function calculateSubscriptionCost(
  basePrice: number,
  billingPeriod: 'monthly' | 'yearly',
  currency: string,
  exchangeRates: Record<string, number>
): number {
  let price = basePrice;

  // Apply 7% discount for yearly billing
  if (billingPeriod === 'yearly') {
    price = basePrice * 0.93;
  }

  // Convert to selected currency
  const rate = exchangeRates[currency] || 1;
  return parseFloat((price * rate).toFixed(2));
}

/**
 * Get setup fee for tier
 * Based on pricing table: tier1-4 = £199, tier5 = £299
 */
export function getSetupFee(tierId: string): number {
  const setupFees: Record<string, number> = {
    tier1: 199,
    tier2: 199,
    tier3: 199,
    tier4: 199,
    tier5: 299,
  };

  return setupFees[tierId] || 199;
}

/**
 * Get or create a customer by email
 * Prevents duplicate customer creation
 */
export async function getOrCreateCustomer(params: {
  email: string;
  name?: string;
  metadata?: Record<string, string>;
}): Promise<Stripe.Customer> {
  try {
    // Check if customer already exists
    const existingCustomers = await stripe.customers.list({
      email: params.email,
      limit: 1,
    });

    if (existingCustomers.data.length > 0) {
      return existingCustomers.data[0];
    }

    // Create new customer
    return await createCustomer({
      email: params.email,
      name: params.name,
      metadata: params.metadata,
    });
  } catch (error) {
    console.error('Error getting or creating customer:', error);
    throw error;
  }
}

/**
 * Calculate prorated amount for subscription tier changes
 * @param currentPrice Current tier monthly price
 * @param newPrice New tier monthly price
 * @param daysRemaining Days remaining in current billing period
 * @param billingPeriodDays Total days in billing period
 * @returns Prorated amount (positive = charge, negative = credit)
 */
export function calculateProration(params: {
  currentPrice: number;
  newPrice: number;
  daysRemaining: number;
  totalDaysInPeriod: number;
}): number {
  const { currentPrice, newPrice, daysRemaining, totalDaysInPeriod } = params;

  // Calculate unused portion of current subscription
  const unusedCurrent = (currentPrice / totalDaysInPeriod) * daysRemaining;

  // Calculate cost of new subscription for remaining period
  const newCost = (newPrice / totalDaysInPeriod) * daysRemaining;

  // Return difference rounded to nearest integer (positive = user pays, negative = user gets credit)
  return Math.round(newCost - unusedCurrent);
}

/**
 * Upgrade a subscription to a higher tier
 * Handles Stripe subscription update and proration calculation
 */
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 {
    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');
    }

    const upcomingInvoice = await stripe.invoices.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) => ({
        id: line.id,
        description: line.description,
        amount: line.amount,
        proration: line.proration,
        period: line.period,
      })),
    };
  } catch (error) {
    console.error('Error previewing subscription upgrade:', error);
    throw error;
  }
}

/**
 * Preview plan change using Stripe's invoices.upcoming API
 * Returns accurate proration amounts directly from Stripe
 */
export async function previewPlanChange(params: {
  subscriptionId: string; // Prisma DB subscription ID
  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
  const upcomingInvoice = await stripe.invoices.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 => ({
      description: line.description,
      amount: line.amount,
      proration: line.proration || false,
    })),
  };
}

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, any>;
}> {
  const { subscriptionId, newTierId, currency, billingPeriod, resetTrial, createImmediateInvoice, language } = params;

  try {
    // 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
    const upcomingInvoice = await stripe.invoices.upcoming({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      subscription_items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
    });

    // Keep amount in cents (don't convert)
    const prorationAmount = upcomingInvoice.amount_due;

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

    // Add language if provided
    if (language) {
      updateParams.metadata.language = language;
    }

    // Add trial_end if resetTrial is requested (7 days from now)
    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 7);
      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: any = {
      tierId: newTierId,
      stripePriceId: newPriceId,
    };

    // Add trialEndsAt if trial is being reset
    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 7);
      dbUpdateData.trialEndsAt = trialEndDate;
    }

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

    // 8. Record billing history event for upgrade
    const { createId } = await import('@paralleldrive/cuid2');
    const upgradeHistoryId = createId();
    await billingHistory.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,
        },
      },
    });

    // 9. Record billing history event for immediate invoice if created
    if (immediateInvoice) {
      const invoiceHistoryId = createId();
      await billingHistory.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,
          },
        },
      });

    }

    const result: any = {
      prorationAmount,
      newTierId,
    };

    // Include trialEndsAt if trial was reset
    if (resetTrial) {
      const trialEndDate = new Date();
      trialEndDate.setDate(trialEndDate.getDate() + 7);
      result.trialEndsAt = trialEndDate;
    }

    // Include invoiceId if immediate invoice was created
    if (immediateInvoice) {
      result.invoiceId = immediateInvoice.id;
    }

    // Include metadata from updated subscription
    if (updatedSubscription.metadata) {
      result.metadata = updatedSubscription.metadata;
    }

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

/**
 * Downgrade a subscription to a lower tier
 * Applies credit for unused portion of current 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 {
    // 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
    const upcomingInvoice = await stripe.invoices.upcoming({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      subscription_items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
    });

    // For downgrades, amount_due is negative (customer gets credit)
    const prorationCredit = upcomingInvoice.amount_due; // Keep negative value

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

    // 6. Update database subscription record
    await subscriptionsAdapter.update({
      where: { id: subscriptionId },
      data: {
        tierId: newTierId,
        stripePriceId: newPriceId,
      },
    });

    // 7. Record billing history event
    await billingHistory.create({
      data: {
        customerId: currentSubscription.stripeCustomerId,
        subscriptionId: subscriptionId,
        event: 'subscription_downgraded',
        amount: prorationCredit, // Negative value (credit)
        currency: currency,
        metadata: {
          from_tier: currentSubscription.tierId,
          to_tier: newTierId,
          proration_credit: prorationCredit, // Store negative value
        },
      },
    });

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

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

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

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

    // 2. Find price for same tier but different billing period
    const newPriceId = await findPriceForTier({
      tierId: currentSubscription.tierId,
      currency: 'gbp', // Default currency from subscription
      billingPeriod: newBillingPeriod,
    });

    // 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. Update Stripe subscription (no proration calculation for billing period changes)
    await stripe.subscriptions.update(currentSubscription.stripeSubscriptionId, {
      items: [
        {
          id: subscriptionItemId,
          price: newPriceId,
        },
      ],
      proration_behavior: 'create_prorations',
      metadata: {
        tier_id: currentSubscription.tierId,
        billing_period: newBillingPeriod,
      },
    });

    // 5. Update database subscription record
    await subscriptionsAdapter.update({
      where: { id: subscriptionId },
      data: {
        billingPeriod: newBillingPeriod,
        stripePriceId: newPriceId,
      },
    });

    // 6. Record billing history event
    await billingHistory.create({
      data: {
        customerId: currentSubscription.stripeCustomerId,
        subscriptionId: subscriptionId,
        event: 'billing_period_changed',
        amount: 0, // No immediate charge for billing period change
        currency: 'gbp',
        metadata: {
          from_period: currentSubscription.billingPeriod,
          to_period: newBillingPeriod,
        },
      },
    });

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

// ============================================================================
// REFUND FUNCTIONS
// ============================================================================

/**
 * Refund reason types supported by Stripe
 */
export type RefundReason = 'requested_by_customer' | 'duplicate' | 'fraudulent';

/**
 * Create a refund for a payment
 * Can create full or partial refunds
 *
 * @param params - Refund parameters
 * @returns Stripe refund object
 */
export async function createRefund(params: {
  paymentIntentId?: string;
  chargeId?: string;
  amount?: number; // In cents. Omit for full refund
  reason?: RefundReason;
  metadata?: Record<string, string>;
}): Promise<Stripe.Refund> {
  const { paymentIntentId, chargeId, amount, reason, metadata } = params;

  if (!paymentIntentId && !chargeId) {
    throw new Error('Either paymentIntentId or chargeId is required');
  }

  try {
    const refundParams: Stripe.RefundCreateParams = {};

    if (paymentIntentId) {
      refundParams.payment_intent = paymentIntentId;
    } else if (chargeId) {
      refundParams.charge = chargeId;
    }

    if (amount !== undefined && amount > 0) {
      refundParams.amount = amount;
    }

    if (reason) {
      refundParams.reason = reason;
    }

    if (metadata) {
      refundParams.metadata = metadata;
    }

    const refund = await stripe.refunds.create(refundParams);
    return refund;
  } catch (error) {
    console.error('Error creating refund:', error);
    throw error;
  }
}

/**
 * Retrieve a refund by ID
 *
 * @param refundId - Stripe refund ID (re_xxx)
 * @returns Stripe refund object or null
 */
export async function getRefund(refundId: string): Promise<Stripe.Refund | null> {
  try {
    const refund = await stripe.refunds.retrieve(refundId, {
      expand: ['charge', 'payment_intent'],
    });
    return refund;
  } catch (error) {
    console.error('Error retrieving refund:', error);
    return null;
  }
}

/**
 * List refunds for a payment intent or charge
 *
 * @param params - Filter parameters
 * @returns List of refunds
 */
export async function listRefunds(params: {
  paymentIntentId?: string;
  chargeId?: string;
  limit?: number;
}): Promise<Stripe.Refund[]> {
  const { paymentIntentId, chargeId, limit = 10 } = params;

  try {
    const listParams: Stripe.RefundListParams = {
      limit,
    };

    if (paymentIntentId) {
      listParams.payment_intent = paymentIntentId;
    }

    if (chargeId) {
      listParams.charge = chargeId;
    }

    const refunds = await stripe.refunds.list(listParams);
    return refunds.data;
  } catch (error) {
    console.error('Error listing refunds:', error);
    return [];
  }
}

/**
 * Cancel a pending refund
 * Only works if the refund status is 'pending'
 *
 * @param refundId - Stripe refund ID (re_xxx)
 * @returns Cancelled refund or null if failed
 */
export async function cancelRefund(refundId: string): Promise<Stripe.Refund | null> {
  try {
    const refund = await stripe.refunds.cancel(refundId);
    return refund;
  } catch (error) {
    console.error('Error cancelling refund:', error);
    return null;
  }
}

/**
 * Get all refundable payments for a customer
 * Returns payments that can still be refunded (within refund window, not fully refunded)
 *
 * @param customerId - Stripe customer ID
 * @param refundWindowDays - Number of days after payment that refunds are allowed (default: 180)
 * @returns List of refundable payment intents with refund info
 */
export async function getRefundablePayments(
  customerId: string,
  refundWindowDays: number = 180
): Promise<Array<{
  paymentIntent: Stripe.PaymentIntent;
  charge: Stripe.Charge | null;
  amountRefundable: number;
  amountRefunded: number;
  isFullyRefunded: boolean;
  withinRefundWindow: boolean;
}>> {
  try {
    // Get payment intents for this customer
    const paymentIntents = await stripe.paymentIntents.list({
      customer: customerId,
      limit: 100,
      expand: ['data.latest_charge'],
    });

    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - refundWindowDays);
    const cutoffTimestamp = Math.floor(cutoffDate.getTime() / 1000);

    const refundablePayments = paymentIntents.data
      .filter(pi => pi.status === 'succeeded')
      .map(pi => {
        const charge = pi.latest_charge as Stripe.Charge | null;
        const amountRefunded = charge?.amount_refunded || 0;
        const amountRefundable = pi.amount - amountRefunded;
        const isFullyRefunded = amountRefundable <= 0;
        const withinRefundWindow = pi.created >= cutoffTimestamp;

        return {
          paymentIntent: pi,
          charge,
          amountRefundable,
          amountRefunded,
          isFullyRefunded,
          withinRefundWindow,
        };
      })
      .filter(p => !p.isFullyRefunded && p.withinRefundWindow);

    return refundablePayments;
  } catch (error) {
    console.error('Error getting refundable payments:', error);
    return [];
  }
}

/**
 * Get refund history for a customer
 *
 * @param customerId - Stripe customer ID
 * @param limit - Maximum number of refunds to return
 * @returns List of refunds with expanded payment info
 */
export async function getCustomerRefunds(
  customerId: string,
  limit: number = 50
): Promise<Stripe.Refund[]> {
  try {
    // First get all charges for this customer
    const charges = await stripe.charges.list({
      customer: customerId,
      limit: 100,
    });

    // Get refunds for each charge that has been refunded
    const refundedCharges = charges.data.filter(c => c.refunded || c.amount_refunded > 0);

    const allRefunds: Stripe.Refund[] = [];

    for (const charge of refundedCharges) {
      const refunds = await stripe.refunds.list({
        charge: charge.id,
        limit: 10,
      });
      allRefunds.push(...refunds.data);
    }

    // Sort by created date (most recent first) and limit
    return allRefunds
      .sort((a, b) => b.created - a.created)
      .slice(0, limit);
  } catch (error) {
    console.error('Error getting customer refunds:', error);
    return [];
  }
}

// ============================================================================
// ADDITIONAL SEAT SUBSCRIPTION FUNCTIONS
// ============================================================================

/**
 * Get current seat subscription item for an organization
 * Returns the subscription item for additional seats if it exists
 */
export async function getSeatSubscriptionItem(
  subscriptionId: string
): Promise<Stripe.SubscriptionItem | null> {
  try {
    const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
      expand: ['items.data.price.product'],
    });

    // Find the seat subscription item by checking product metadata
    const seatItem = subscription.items.data.find((item) => {
      const product = item.price.product as Stripe.Product;
      return product.metadata?.type === 'additional_seat';
    });

    return seatItem || null;
  } catch (error) {
    console.error('Error getting seat subscription item:', error);
    return null;
  }
}

/**
 * Purchase additional seats for an organization
 * Creates or updates a subscription item for seats
 *
 * @param params - Purchase parameters
 * @returns Success status or error message
 */
export async function purchaseAdditionalSeats(params: {
  subscriptionId: string;
  organizationId: string;
  quantity: number;
  customerId: string;
  currency?: string;
}): Promise<{ success: boolean; error?: string }> {
  const { subscriptionId, organizationId, quantity, customerId, currency = 'gbp' } = params;

  try {
    // Validate quantity
    if (quantity < 1) {
      return { success: false, error: 'Quantity must be at least 1' };
    }

    // Get the Stripe price ID for additional seats
    // This should be configured in Stripe with metadata: type = "additional_seat"
    const prices = await stripe.prices.list({
      limit: 100,
      expand: ['data.product'],
    });

    const normalizedCurrency = currency.toLowerCase();
    const seatPrice = prices.data.find((price) => {
      const product = price.product as Stripe.Product;
      return (
        product.metadata?.type === 'additional_seat' &&
        price.currency === normalizedCurrency &&
        price.recurring?.interval === 'month'
      );
    });

    if (!seatPrice) {
      return {
        success: false,
        error: `Additional seat price not found in Stripe for currency '${normalizedCurrency}'. Please configure the seat product.`
      };
    }

    // Check if seat item already exists
    const existingSeatItem = await getSeatSubscriptionItem(subscriptionId);

    if (existingSeatItem) {
      // Update existing seat quantity (add to current quantity)
      const newQuantity = (existingSeatItem.quantity || 0) + quantity;
      await stripe.subscriptionItems.update(existingSeatItem.id, {
        quantity: newQuantity,
        proration_behavior: 'create_prorations',
      });
    } else {
      // Add new seat subscription item
      await stripe.subscriptionItems.create({
        subscription: subscriptionId,
        price: seatPrice.id,
        quantity: quantity,
        proration_behavior: 'create_prorations',
        metadata: {
          organization_id: organizationId,
          type: 'additional_seats',
        },
      });
    }

    // Create and pay invoice immediately for the prorated amount
    try {
      const invoice = await stripe.invoices.create({
        customer: customerId,
        subscription: subscriptionId,
        auto_advance: true, // Auto-finalize
      });

      // Pay the invoice immediately if there's an amount due
      if (invoice.amount_due > 0) {
        await stripe.invoices.pay(invoice.id);
      }
    } catch (invoiceError) {
      // Invoice might fail if there's nothing to invoice (e.g., trial period)
      // This is okay - the seats are still added
      console.log('Invoice creation skipped or failed (may be in trial):', invoiceError);
    }

    return { success: true };
  } catch (error) {
    console.error('Error purchasing additional seats:', error);
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Failed to purchase seats'
    };
  }
}

/**
 * Update the quantity of additional seats
 * Can increase or decrease (minimum 0)
 *
 * @param params - Update parameters
 * @returns Success status or error message
 */
export async function updateSeatQuantity(params: {
  subscriptionId: string;
  organizationId: string;
  newQuantity: number;
}): Promise<{ success: boolean; error?: string }> {
  const { subscriptionId, organizationId, newQuantity } = params;

  try {
    // Validate quantity
    if (newQuantity < 0) {
      return { success: false, error: 'Quantity cannot be negative' };
    }

    // Get existing seat subscription item
    const seatItem = await getSeatSubscriptionItem(subscriptionId);

    if (!seatItem) {
      if (newQuantity === 0) {
        // No item exists and quantity is 0, nothing to do
        return { success: true };
      }

      // Need to create new item
      return {
        success: false,
        error: 'No existing seat subscription. Use purchaseAdditionalSeats instead.'
      };
    }

    if (newQuantity === 0) {
      // Remove the seat subscription item entirely
      await stripe.subscriptionItems.del(seatItem.id, {
        proration_behavior: 'create_prorations',
      });
    } else {
      // Update quantity
      await stripe.subscriptionItems.update(seatItem.id, {
        quantity: newQuantity,
        proration_behavior: 'create_prorations',
      });
    }

    return { success: true };
  } catch (error) {
    console.error('Error updating seat quantity:', error);
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Failed to update seats'
    };
  }
}

/**
 * Create Stripe checkout session for purchasing seats
 * Allows users to purchase seats with immediate payment
 *
 * @param params - Checkout session parameters
 * @returns Session ID or error message
 */
export async function createSeatCheckoutSession(params: {
  customerId: string;
  organizationId: string;
  quantity: number;
  successUrl: string;
  cancelUrl: string;
  currency?: string;
}): Promise<{ sessionId: string } | { error: string }> {
  const { customerId, organizationId, quantity, successUrl, cancelUrl, currency } = params;

  try {
    // Validate quantity
    if (quantity < 1) {
      return { error: 'Quantity must be at least 1' };
    }

    // Get customer's current subscription to determine currency
    const subscriptions = await stripe.subscriptions.list({
      customer: customerId,
      status: 'active',
      limit: 1,
    });

    if (!subscriptions.data.length) {
      return { error: 'No active subscription found' };
    }

    const subscription = subscriptions.data[0];
    const subscriptionCurrency = (currency || subscription.currency || 'gbp').toLowerCase();

    // Get the seat price matching the subscription currency
    const prices = await stripe.prices.list({
      limit: 100,
      expand: ['data.product'],
    });

    const seatPrice = prices.data.find((price) => {
      const product = price.product as Stripe.Product;
      return (
        product.metadata?.type === 'additional_seat' &&
        price.currency === subscriptionCurrency &&
        price.recurring?.interval === 'month'
      );
    });

    if (!seatPrice) {
      return { error: `Additional seat price not found in Stripe for currency '${subscriptionCurrency}'` };
    }

    // Check if seats already exist
    const existingSeatItem = await getSeatSubscriptionItem(subscription.id);
    const currentQuantity = existingSeatItem?.quantity || 0;
    const newQuantity = currentQuantity + quantity;

    // Create checkout session for seat subscription update
    const session = await stripe.checkout.sessions.create({
      mode: 'subscription',
      customer: customerId,
      line_items: [
        {
          price: seatPrice.id,
          quantity: quantity,
        },
      ],
      subscription_data: {
        metadata: {
          organization_id: organizationId,
          seat_purchase: 'true',
          previous_quantity: currentQuantity.toString(),
          new_quantity: newQuantity.toString(),
        },
      },
      success_url: successUrl,
      cancel_url: cancelUrl,
      allow_promotion_codes: false,
      billing_address_collection: 'auto',
    });

    return { sessionId: session.id };
  } catch (error) {
    console.error('Error creating seat checkout session:', error);
    return { error: error instanceof Error ? error.message : 'Failed to create checkout session' };
  }
}
