import Stripe from "stripe";
import stripePricesConfig from "@/lib/config/stripe-prices.json";
import {
  getConvexSubscriptionByAnyId,
  recordConvexBillingEvent,
  updateConvexSubscription,
} from "@/lib/convex-billing-state";
import { findPriceForTier } from "@/lib/stripe/pricing";
import { stripe } from "@/lib/stripe/client";

interface UpcomingInvoiceLineItem {
  id: string;
  amount: number;
  description: string | null;
  proration: boolean;
  period: { start: number; end: number };
}

interface UpcomingInvoiceResponse {
  amount_due: number;
  currency: string;
  next_payment_attempt: number | null;
  period_end: number;
  lines: {
    data: UpcomingInvoiceLineItem[];
  };
}

async function getStoredSubscription(subscriptionId: string) {
  const subscription = await getConvexSubscriptionByAnyId(subscriptionId);

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

  return subscription;
}

async function getUpcomingInvoice(params: {
  customer: string;
  subscription: string;
  subscriptionItemId: string;
  priceId: string;
  expand?: string[];
}) {
  return await (
    stripe.invoices as unknown as {
      upcoming: (args: {
        customer: string;
        subscription: string;
        subscription_items: Array<{ id: string; price: string }>;
        expand?: string[];
      }) => Promise<UpcomingInvoiceResponse>;
    }
  ).upcoming({
    customer: params.customer,
    subscription: params.subscription,
    subscription_items: [
      {
        id: params.subscriptionItemId,
        price: params.priceId,
      },
    ],
    expand: params.expand,
  });
}

function getStripePriceIdForPlanChange(
  tierId: string,
  billingPeriod: "monthly" | "yearly",
) {
  const tierPrices =
    stripePricesConfig.tiers[
      tierId as keyof typeof stripePricesConfig.tiers
    ];

  if (!tierPrices) {
    throw new Error(`Invalid tier: ${tierId}`);
  }

  return billingPeriod === "monthly" ? tierPrices.monthly : tierPrices.yearly;
}

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 subscription = await getStoredSubscription(params.subscriptionId);
  const priceId = getStripePriceIdForPlanChange(
    params.newTierId,
    params.billingPeriod,
  );

  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");
  }

  const upcomingInvoice = await getUpcomingInvoice({
    customer: subscription.stripeCustomerId,
    subscription: subscription.stripeSubscriptionId,
    subscriptionItemId,
    priceId,
  });

  let creditFromCurrent = 0;
  let chargeForNew = 0;

  for (const line of upcomingInvoice.lines.data) {
    if (!line.proration) continue;
    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 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 currentSubscription = await getStoredSubscription(params.subscriptionId);
  const priceId = await findPriceForTier({
    tierId: params.newTierId,
    currency: params.currency,
    billingPeriod: params.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 getUpcomingInvoice({
    customer: currentSubscription.stripeCustomerId,
    subscription: currentSubscription.stripeSubscriptionId,
    subscriptionItemId,
    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,
    })),
  };
}

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 currentSubscription = await getStoredSubscription(params.subscriptionId);
  const newPriceId = await findPriceForTier({
    tierId: params.newTierId,
    currency: params.currency,
    billingPeriod: params.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");
  }

  const upcomingInvoice = await getUpcomingInvoice({
    customer: currentSubscription.stripeCustomerId,
    subscription: currentSubscription.stripeSubscriptionId,
    subscriptionItemId,
    priceId: newPriceId,
  });
  const prorationAmount = upcomingInvoice.amount_due;

  const updateParams: Stripe.SubscriptionUpdateParams = {
    items: [{ id: subscriptionItemId, price: newPriceId }],
    proration_behavior: "create_prorations",
    metadata: {
      ...fullSubscription.metadata,
      tier_id: params.newTierId,
      billing_period: params.billingPeriod,
      ...(params.language ? { language: params.language } : {}),
    },
  };

  let trialEndsAt: Date | undefined;
  if (params.resetTrial) {
    trialEndsAt = new Date();
    trialEndsAt.setDate(trialEndsAt.getDate() + 14);
    updateParams.trial_end = Math.floor(trialEndsAt.getTime() / 1000);
  }

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

  let immediateInvoice: Stripe.Invoice | null = null;
  const canCreateImmediateInvoice =
    params.createImmediateInvoice !== false &&
    currentSubscription.status !== "trialing" &&
    !currentSubscription.trialEndsAt;

  if (canCreateImmediateInvoice) {
    immediateInvoice = await stripe.invoices.create({
      customer: currentSubscription.stripeCustomerId,
      subscription: currentSubscription.stripeSubscriptionId,
      auto_advance: true,
    });
  }

  await updateConvexSubscription({
    id: currentSubscription._id,
    tierId: params.newTierId,
    stripePriceId: newPriceId,
    billingPeriod: params.billingPeriod,
    trialEndsAt: trialEndsAt?.getTime(),
    status: updatedSubscription.status,
  });

  await recordConvexBillingEvent({
    customerId: currentSubscription.stripeCustomerId,
    subscriptionId: currentSubscription.stripeSubscriptionId,
    dedupeKey: `subscription_upgraded:${currentSubscription.stripeSubscriptionId}:${newPriceId}:${params.billingPeriod}`,
    event: "subscription_upgraded",
    amount: prorationAmount,
    currency: params.currency,
    metadata: {
      from_tier: currentSubscription.tierId,
      to_tier: params.newTierId,
      proration_amount: prorationAmount,
    },
  });

  if (immediateInvoice) {
    await recordConvexBillingEvent({
      customerId: currentSubscription.stripeCustomerId,
      subscriptionId: currentSubscription.stripeSubscriptionId,
      invoiceId: immediateInvoice.id,
      dedupeKey: `upgrade_invoice_created:${immediateInvoice.id}`,
      event: "upgrade_invoice_created",
      amount: immediateInvoice.amount_due,
      currency: params.currency,
      metadata: {
        from_tier: currentSubscription.tierId,
        to_tier: params.newTierId,
        proration_amount: immediateInvoice.amount_due,
      },
    });
  }

  return {
    prorationAmount,
    newTierId: params.newTierId,
    trialEndsAt,
    invoiceId: immediateInvoice?.id,
    metadata: updatedSubscription.metadata
      ? (updatedSubscription.metadata as Record<string, unknown>)
      : undefined,
  };
}

export async function downgradeSubscription(params: {
  subscriptionId: string;
  newTierId: string;
  currency: string;
  billingPeriod: "monthly" | "yearly";
}): Promise<{
  prorationCredit: number;
  newTierId: string;
}> {
  const currentSubscription = await getStoredSubscription(params.subscriptionId);
  const newPriceId = await findPriceForTier({
    tierId: params.newTierId,
    currency: params.currency,
    billingPeriod: params.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");
  }

  const upcomingInvoice = await getUpcomingInvoice({
    customer: currentSubscription.stripeCustomerId,
    subscription: currentSubscription.stripeSubscriptionId,
    subscriptionItemId,
    priceId: newPriceId,
  });

  const prorationCredit = upcomingInvoice.amount_due;

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

  await updateConvexSubscription({
    id: currentSubscription._id,
    tierId: params.newTierId,
    stripePriceId: newPriceId,
    billingPeriod: params.billingPeriod,
    status: updatedSubscription.status,
  });

  await recordConvexBillingEvent({
    customerId: currentSubscription.stripeCustomerId,
    subscriptionId: currentSubscription.stripeSubscriptionId,
    dedupeKey: `subscription_downgraded:${currentSubscription.stripeSubscriptionId}:${newPriceId}:${params.billingPeriod}`,
    event: "subscription_downgraded",
    amount: prorationCredit,
    currency: params.currency,
    metadata: {
      from_tier: currentSubscription.tierId,
      to_tier: params.newTierId,
      proration_credit: prorationCredit,
    },
  });

  return {
    prorationCredit,
    newTierId: params.newTierId,
  };
}

export async function changeBillingPeriod(params: {
  subscriptionId: string;
  newBillingPeriod: "monthly" | "yearly";
}): Promise<{
  billingPeriod: "monthly" | "yearly";
}> {
  const currentSubscription = await getStoredSubscription(params.subscriptionId);
  const newPriceId = await findPriceForTier({
    tierId: currentSubscription.tierId,
    currency: "gbp",
    billingPeriod: params.newBillingPeriod,
  });

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

  if (!subscriptionItemId) {
    throw new Error("Subscription item not found");
  }

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

  await updateConvexSubscription({
    id: currentSubscription._id,
    billingPeriod: params.newBillingPeriod,
    stripePriceId: newPriceId,
  });

  await recordConvexBillingEvent({
    customerId: currentSubscription.stripeCustomerId,
    subscriptionId: currentSubscription.stripeSubscriptionId,
    dedupeKey: `billing_period_changed:${currentSubscription.stripeSubscriptionId}:${params.newBillingPeriod}`,
    event: "billing_period_changed",
    amount: 0,
    currency: "gbp",
    metadata: {
      from_period: currentSubscription.billingPeriod,
      to_period: params.newBillingPeriod,
    },
  });

  return {
    billingPeriod: params.newBillingPeriod,
  };
}
