import { APIRequestContext } from '@playwright/test';
import {
  fromInvoiceFetchApiResponse,
  toInvoiceCreateApiPayload,
  toInvoiceIssueCreditNoteApiPayload,
} from '@/manager/modules/invoice/invoice-mappers';
import {
  InvoiceCreateApiResponse,
  InvoiceCreateData,
  InvoiceFetchApiResponse,
  InvoiceIssueCreditNoteData,
} from '@/manager/modules/invoice/invoice-types';
import { Invoice } from '@/manager/modules/invoice/invoice-types';
import { fetchLocationDefaultPaymentMethodIdsViaApi } from '@/manager/modules/location/location-api';
import { isModernBillingMode } from '@/shared/utils/billing-utils';
import dayjs from 'dayjs';

// The manager API serves invoice writes under /v1; the billing API (modern Kinnovis billing) serves them under
// /api/v1, and in modern mode the manager API creates invoices against Stripe prices the tenant no longer has.
// Mirrors the manager SPA's kbInvoiceService: create, credit notes and mark-as-paid go through `request`, while
// reads (invoice fetch, location payment methods) stay on `managerRequest` in both modes.
export interface InvoiceApiClient {
  request: APIRequestContext;
  basePath: string;
  managerRequest: APIRequestContext;
}

const managerInvoiceBasePath = '/v1';
const billingInvoiceBasePath = '/api/v1';

export function resolveInvoiceApiClient(
  managerApiRequest: APIRequestContext,
  billingApiRequest: APIRequestContext
): InvoiceApiClient {
  return isModernBillingMode()
    ? { request: billingApiRequest, basePath: billingInvoiceBasePath, managerRequest: managerApiRequest }
    : { request: managerApiRequest, basePath: managerInvoiceBasePath, managerRequest: managerApiRequest };
}

export async function createInvoiceViaApi(client: InvoiceApiClient, data: InvoiceCreateData): Promise<Invoice> {
  const defaultPaymentMethodIds = await fetchLocationDefaultPaymentMethodIdsViaApi(
    client.managerRequest,
    data.location.id
  );

  const response = await client.request.post(`${client.basePath}/invoice`, {
    data: toInvoiceCreateApiPayload(data, defaultPaymentMethodIds),
  });

  if (!response.ok()) {
    throw new Error(`Failed to create invoice via API: ${response.status()} ${await response.text()}`);
  }

  const { id } = ((await response.json()) as InvoiceCreateApiResponse).data;
  const body = await fetchInvoiceViaApi(client.managerRequest, id);

  return fromInvoiceFetchApiResponse(id, body, data);
}

async function fetchInvoiceViaApi(request: APIRequestContext, id: number): Promise<InvoiceFetchApiResponse> {
  const response = await request.get(`/v1/invoice/${id}`);

  if (!response.ok()) {
    throw new Error(`Failed to fetch invoice ${id} via API: ${response.status()} ${await response.text()}`);
  }

  return (await response.json()) as InvoiceFetchApiResponse;
}

// Whether a credit note can be issued for the invoice right now — false while the invoice's charge is still
// settling (pending payment) or the API forbids crediting it for any other reason.
export async function fetchInvoiceCanBeCreditedViaApi(request: APIRequestContext, id: number): Promise<boolean> {
  const body = await fetchInvoiceViaApi(request, id);

  return !body.data.has_pending_payment && body.data.actions.can_issue_credit_note;
}

// The credit endpoint identifies what to credit by invoice line-item ids, which only exist server-side — so
// they are fetched here rather than carried on the Invoice domain type. The response is the updated invoice,
// not the credit note — the credit note itself has no read endpoint, and its number is derivable via
// toInvoiceCreditNoteNumber, so nothing from the response is needed.
export async function issueInvoiceCreditNoteViaApi(
  client: InvoiceApiClient,
  id: number,
  data: InvoiceIssueCreditNoteData
): Promise<void> {
  const body = await fetchInvoiceViaApi(client.managerRequest, id);

  const response = await client.request.post(`${client.basePath}/credit-note`, {
    data: toInvoiceIssueCreditNoteApiPayload(data, id, body.data.line_items),
  });

  if (!response.ok()) {
    throw new Error(
      `Failed to issue credit note for invoice ${id} via API: ${response.status()} ${await response.text()}`
    );
  }
}

// Marks an open invoice paid with an out-of-band cash payment — purely internal, no Stripe charge or email.
// Used to stand up the 'paid' invoices the credit-note specs need without a real card charge.
export async function markInvoiceAsPaidViaApi(client: InvoiceApiClient, id: number): Promise<void> {
  const response = await client.request.put(`${client.basePath}/invoice/${id}/status-paid`, {
    data: { payment_date: dayjs().format('YYYY-MM-DD'), payment_type: 'cash' },
  });

  if (!response.ok()) {
    throw new Error(`Failed to mark invoice ${id} as paid via API: ${response.status()} ${await response.text()}`);
  }
}
