import { AutumnInt } from "@/external/autumn/autumnCli.js";
import {
  AppEnv,
  AttachBranch,
  BillingInterval,
  FeatureOptions,
  Organization,
  ProductItem,
  ProductV2,
  UsagePriceConfig,
} from "@autumn/shared";
import { getAttachTotal } from "tests/utils/testAttachUtils/testAttachUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { expectInvoicesCorrect } from "tests/utils/expectUtils/expectProductAttached.js";
import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js";
import { notNullish, nullish, timeout, toSnakeCase } from "@/utils/genUtils.js";
import {
  expectSubItemsCorrect,
  getSubsFromCusId,
} from "tests/utils/expectUtils/expectSubUtils.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import Stripe from "stripe";
import { expect } from "chai";
import {
  cusProductToEnts,
  cusProductToPrices,
} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
import { getPriceEntitlement } from "@/internal/products/prices/priceUtils.js";
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
import { Decimal } from "decimal.js";
import { isFreeProductV2 } from "@/internal/products/productUtils/classifyProduct.js";

export const runAttachTest = async ({
  autumn,
  customerId,
  entityId,
  product,
  options,
  stripeCli,
  db,
  org,
  env,
  usage,
  waitForInvoice = 0,
  isCanceled = false,
  skipFeatureCheck = false,
  singleInvoice = false,
}: {
  autumn: AutumnInt;
  customerId: string;
  entityId?: string;
  product: ProductV2;
  options?: FeatureOptions[];
  stripeCli: Stripe;
  db: DrizzleCli;
  org: Organization;
  env: AppEnv;
  usage?: {
    featureId: string;
    value: number;
  }[];
  waitForInvoice?: number;
  isCanceled?: boolean;
  skipFeatureCheck?: boolean;
  singleInvoice?: boolean;
}) => {
  const preview = await autumn.attachPreview({
    customer_id: customerId,
    product_id: product.id,
    entity_id: entityId,
  });

  const total = getAttachTotal({
    preview,
    options,
  });

  await autumn.attach({
    customer_id: customerId,
    product_id: product.id,
    entity_id: entityId,
    options: toSnakeCase(options),
  });

  if (waitForInvoice) {
    await timeout(waitForInvoice);
  }

  const customer = await autumn.customers.get(customerId);

  const productCount = customer.products.reduce((acc: number, p: any) => {
    if (product.group == p.group) {
      return acc + 1;
    } else return acc;
  }, 0);

  expect(
    productCount,
    `customer should only have 1 product (from this group: ${product.group})`,
  ).to.equal(1);

  expectProductAttached({
    customer,
    product,
    entityId,
  });

  let intervals = Array.from(
    new Set(product.items.map((item) => item.interval)),
  ).filter(notNullish);
  const multiInterval = intervals.length > 1;

  const freeProduct = isFreeProductV2({ product });
  if (!freeProduct) {
    let multiInvoice = !singleInvoice && multiInterval;
    expectInvoicesCorrect({
      customer,
      first: multiInvoice ? undefined : { productId: product.id, total },
      second: multiInvoice ? { productId: product.id, total } : undefined,
    });
  }

  if (!skipFeatureCheck) {
    expectFeaturesCorrect({
      customer,
      product,
      usage,
      options,
    });
  }

  const branch = preview.branch;
  if (branch == AttachBranch.OneOff || freeProduct) {
    return;
  }
  await expectSubItemsCorrect({
    stripeCli,
    customerId,
    product,
    db,
    org,
    env,
    isCanceled,
  });

  const stripeSubs = await stripeCli.subscriptions.list({
    customer: customer.stripe_id!,
  });
  if (multiInterval) {
    expect(stripeSubs.data.length).to.equal(2, "should have 2 subscriptions");
  } else {
    expect(stripeSubs.data.length).to.equal(
      1,
      "should only have 1 subscription",
    );
  }
};

export const addPrefixToProducts = ({
  products,
  prefix,
}: {
  products: ProductV2[];
  prefix: string;
}) => {
  for (const product of products) {
    product.id = `${prefix}_${product.id}`;
    product.name = `${prefix} ${product.name}`;
    product.group = prefix;
  }

  return products;
};

export const replaceItems = ({
  featureId,
  interval,
  newItem,
  items,
}: {
  featureId?: string;
  interval?: BillingInterval;
  newItem: ProductItem;
  items: ProductItem[];
}) => {
  let newItems = structuredClone(items);

  let index;
  if (featureId) {
    index = newItems.findIndex((item) => item.feature_id == featureId);
  }

  if (interval) {
    index = newItems.findIndex(
      (item) => item.interval == (interval as any) && nullish(item.feature_id),
    );
  }

  if (index == -1) {
    throw new Error("Item not found");
  }

  newItems[index!] = newItem;

  return newItems;
};
