import { DrizzleCli } from "@/db/initDrizzle.js";
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
import {
  findStripeItemForPrice,
  isLicenseItem,
} from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import { CusService } from "@/internal/customers/CusService.js";
import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils.js";
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";
import { nullish } from "@/utils/genUtils.js";
import {
  AppEnv,
  CusProductStatus,
  FullCusProduct,
  Organization,
  ProductV2,
} from "@autumn/shared";
import { expect } from "chai";
import { getDate } from "date-fns";

import Stripe from "stripe";

export const getSubsFromCusId = async ({
  stripeCli,
  customerId,
  productId,
  db,
  org,
  env,
  withExpired = false,
}: {
  stripeCli: Stripe;
  customerId: string;
  productId: string;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
  withExpired?: boolean;
}) => {
  const fullCus = await CusService.getFull({
    db,
    idOrInternalId: customerId,
    orgId: org.id,
    env,
    inStatuses: withExpired
      ? [
          CusProductStatus.Active,
          CusProductStatus.Expired,
          CusProductStatus.PastDue,
          CusProductStatus.Scheduled,
        ]
      : undefined,
  });

  const cusProduct = fullCus.customer_products.find(
    (cp: FullCusProduct) => cp.product.id == productId,
  )!;

  const subs: Stripe.Subscription[] = await getStripeSubs({
    stripeCli,
    subIds: cusProduct?.subscription_ids,
  });

  return {
    fullCus,
    cusProduct,
    subs,
  };
};

export const expectSubAnchorsSame = async ({
  stripeCli,
  customerId,
  productId,
  db,
  org,
  env,
}: {
  stripeCli: Stripe;
  customerId: string;
  productId: string;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
}) => {
  const fullCus = await CusService.getFull({
    db,
    idOrInternalId: customerId,
    orgId: org.id,
    env,
  });

  const cusProduct = fullCus.customer_products.find(
    (cp: FullCusProduct) => cp.product.id == productId,
  );

  const subs: Stripe.Subscription[] = await getStripeSubs({
    stripeCli,
    subIds: cusProduct?.subscription_ids,
  });

  let periodEnd = subs[0].current_period_end * 1000;
  let firstDate = getDate(periodEnd);

  for (const sub of subs.slice(1)) {
    let dateOfAnchor = getDate(sub.current_period_end * 1000);
    expect(dateOfAnchor).to.equal(
      firstDate,
      `subscription anchors are the same`,
    );
  }

  return {
    fullCus,
    subs,
  };
};

export const expectSubItemsCorrect = async ({
  stripeCli,
  customerId,
  product,
  db,
  org,
  env,
  isCanceled = false,
  entityId,
}: {
  stripeCli: Stripe;
  customerId: string;
  product: ProductV2;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
  isCanceled?: boolean;
  entityId?: string;
}) => {
  const fullCus = await CusService.getFull({
    db,
    idOrInternalId: customerId,
    orgId: org.id,
    env,
    withEntities: true,
  });

  let entity = entityId ? fullCus.entities.find((e) => e.id == entityId) : null;

  const productId = product.id;
  const cusProduct = fullCus.customer_products.find(
    (cp: FullCusProduct) =>
      cp.product.id == productId &&
      (entity ? cp.internal_entity_id == entity.internal_id : true),
  )!;

  if (isCanceled) {
    expect(cusProduct.canceled_at, "cus product should be canceled").to.exist;
  } else {
    expect(cusProduct.canceled_at, "cus product should not be canceled").to.not
      .exist;
  }

  if (isFreeProductV2({ product })) {
    expect(
      cusProduct.subscription_ids,
      `cus product should have no subs for free product: ${product.name}`,
    ).to.be.empty;
    return {
      fullCus,
    };
  }

  const subs: Stripe.Subscription[] = await getStripeSubs({
    stripeCli,
    subIds: cusProduct?.subscription_ids,
  });

  for (const sub of subs) {
    if (isCanceled) {
      expect(sub.canceled_at, "sub should be canceled").to.exist;
    } else {
      expect(sub.canceled_at, "sub should not be canceled").to.be.null;
    }
  }

  const subItems = subs.flatMap((sub) => sub.items.data);
  const prices = cusProductToPrices({ cusProduct });

  let missingUsageCount = 0;

  for (const price of prices) {
    const subItem = findStripeItemForPrice({
      stripeItems: subItems,
      price,
    });

    // 1. If usage + v4 + internalEntityId
    if (isV4Usage({ price, cusProduct })) {
      if (nullish(subItem)) {
        missingUsageCount++;
      }

      expect(
        nullish(subItem) ||
          (subItem?.quantity === 0 && isLicenseItem({ stripeItem: subItem! })),
      ).to.be.true;
      continue;
    } else {
      expect(
        subItem,
        `sub item for price: ${(price.config as any).internal_feature_id || price.config.interval} should exist`,
      ).to.exist;
    }
  }

  expect(
    prices.length - missingUsageCount,
    "number of sub items equivalent to number of prices",
  ).to.equal(subItems.length);

  // Expect sub anchors to be the same
  let periodEnd = subs[0].current_period_end * 1000;
  let firstDate = getDate(periodEnd);

  for (const sub of subs.slice(1)) {
    let dateOfAnchor = getDate(sub.current_period_end * 1000);
    expect(dateOfAnchor).to.equal(
      firstDate,
      `subscription anchors are the same`,
    );
  }

  return {
    fullCus,

    cusProduct,
  };
};
