import { createInvoiceViaApi, markInvoiceAsPaidViaApi } from '@/manager/modules/invoice/invoice-api';
import { getInvoiceCreateData, InvoiceCreateDataOptions } from '@/manager/modules/invoice/invoice-factories';
import { Invoice, InvoiceCreateData } from '@/manager/modules/invoice/invoice-types';
import { InvoiceCreateDialog } from '@/manager/modules/invoice/views/InvoiceCreateDialog';
import { InvoiceDetailsPage } from '@/manager/modules/invoice/views/InvoiceDetailsPage';
import { InvoiceListPage } from '@/manager/modules/invoice/views/InvoiceListPage';
import { createCustomerViaApi, deleteCustomerViaApi } from '@/manager/modules/customer/customer-api';
import { getCustomerCreateData } from '@/manager/modules/customer/customer-factories';
import { CustomerCreateData } from '@/manager/modules/customer/customer-types';
import { createProductOneTimeViaApi, deleteProductOneTimeViaApi } from '@/manager/modules/product/product-api';
import { getProductOneTimeCreateData } from '@/manager/modules/product/product-factories';
import { ProductOneTimeCreateData } from '@/manager/modules/product/product-types';
import { test as apiTest } from '@/manager/shared/api-fixtures';
import { locations } from '@/shared/data/seed-locations';
import { Customer, CustomerType } from '@/shared/types/customer-types';
import { Location } from '@/shared/types/location-types';
import { ProductOneTime } from '@/shared/types/product-types';

// Directive describing the dependencies setupInvoiceCreateData stands up over the API before assembling an
// invoice. `numberOfProducts` one-time products are created at the invoice location. The complement is
// InvoiceCreateDataOptions, which only assembles the already-created deps.
export interface InvoiceCreateDataSetup {
  customerType?: CustomerType;
  numberOfProducts?: number;
  location?: Location;
}

export interface InvoiceFixtures {
  invoiceListPage: InvoiceListPage;
  invoiceDetailsPage: InvoiceDetailsPage;
  invoiceCreateDialog: InvoiceCreateDialog;
  setupInvoiceCreateData: (
    setup?: InvoiceCreateDataSetup,
    options?: InvoiceCreateDataOptions
  ) => Promise<InvoiceCreateData>;
  createCustomer: (data: CustomerCreateData) => Promise<Customer>;
  createProductOneTime: (data: ProductOneTimeCreateData) => Promise<ProductOneTime>;
  createInvoice: (data: InvoiceCreateData) => Promise<Invoice>;
  markInvoiceAsPaid: (id: number) => Promise<void>;
}

interface InvoiceInternalFixtures {
  invoiceTeardown: {
    customerIds: number[];
    productOneTimeIds: number[];
  };
}

export const test = apiTest.extend<InvoiceFixtures & InvoiceInternalFixtures>({
  invoiceListPage: async ({ page }, use) => {
    await use(new InvoiceListPage(page));
  },

  invoiceDetailsPage: async ({ page }, use) => {
    await use(new InvoiceDetailsPage(page));
  },

  invoiceCreateDialog: async ({ page }, use) => {
    await use(new InvoiceCreateDialog(page));
  },

  createCustomer: async ({ managerApiRequest, invoiceTeardown }, use) => {
    await use(async (data) => {
      const customer = await apiTest.step('setup: create customer via API', () =>
        createCustomerViaApi(managerApiRequest, data)
      );
      invoiceTeardown.customerIds.push(customer.id);
      return customer;
    });
  },

  createProductOneTime: async ({ managerApiRequest, invoiceTeardown }, use) => {
    await use(async (data) => {
      const product = await apiTest.step('setup: create one-time product via API', () =>
        createProductOneTimeViaApi(managerApiRequest, data)
      );
      invoiceTeardown.productOneTimeIds.push(product.id);
      return product;
    });
  },

  createInvoice: async ({ managerApiRequest }, use) => {
    await use((data) =>
      apiTest.step('setup: create invoice via API', () => createInvoiceViaApi(managerApiRequest, data))
    );
  },

  markInvoiceAsPaid: async ({ managerApiRequest }, use) => {
    await use((id) =>
      apiTest.step('setup: mark invoice as paid via API', () => markInvoiceAsPaidViaApi(managerApiRequest, id))
    );
  },

  // Stands a customer and its one-time products up over the API, then assembles them into an InvoiceCreateData
  // via getInvoiceCreateData. Customer and products share the invoice location so the line items are valid.
  setupInvoiceCreateData: async ({ createCustomer, createProductOneTime }, use) => {
    await use(async (setup = {}, options = {}) => {
      const location = setup.location ?? locations.viennaSouth;

      const [customer, products] = await Promise.all([
        createCustomer(getCustomerCreateData({ type: setup.customerType, locations: [location] })),
        Promise.all(
          Array.from({ length: setup.numberOfProducts ?? 1 }, () =>
            createProductOneTime(getProductOneTimeCreateData({ location }))
          )
        ),
      ]);

      return getInvoiceCreateData({ customer, products }, { ...options, location });
    });
  },

  invoiceTeardown: async ({ managerApiRequest }, use) => {
    const customerIds: number[] = [];
    const productOneTimeIds: number[] = [];

    await use({ customerIds, productOneTimeIds });

    // TODO(KIN-4692): Teardown is disabled for now. Deleting czstiners kicks off a flood of async
    // backend jobs, and when the job queue backs up, invoice-generation jobs don't run in time — which
    // breaks invoice e2e tests that rely on invoices being created. Until the queue pressure is sorted
    // out, we leave all created entities for the env reseed to clean up rather than tearing down here.
    return;

    // Invoices can't be deleted and don't block deleting their customer/product, so they're left as-is (open
    // or paid) for the env reseed — only the customers and products are cleaned up. An invoiced customer or its
    // products may still resist deletion, so every step is best-effort: a teardown failure never fails a
    // passing test.
    const settle = (promise: Promise<unknown>): Promise<unknown> => promise.catch(() => undefined);

    await apiTest.step('teardown: delete created one-time products via API', () =>
      Promise.all(productOneTimeIds.map((id) => settle(deleteProductOneTimeViaApi(managerApiRequest, id))))
    );
    await apiTest.step('teardown: delete created customers via API', () =>
      Promise.all(customerIds.map((id) => settle(deleteCustomerViaApi(managerApiRequest, id))))
    );
  },
});

export { expect } from '@playwright/test';
