import { APIRequestContext } from '@playwright/test';
import {
  fromInvoiceCreateApiResponse,
  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 invoices at /v1/invoice; the billing API (modern Kinnovis billing) serves mark-as-paid
// at /api/v1/invoice, and the manager API rejects mark-as-paid for Kinnovis-billing tenants. Only mark-as-paid
// goes through this client: create, fetch and credit notes stay on the manager API in both modes.
export interface InvoiceApiClient {
  request: APIRequestContext;
  basePath: string;
}

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

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

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

  const response = await request.post('/v1/invoice', {
    data: toInvoiceCreateApiPayload(data, defaultPaymentMethodIds),
  });

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

  const body = (await response.json()) as InvoiceCreateApiResponse;

  return fromInvoiceCreateApiResponse(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(
  request: APIRequestContext,
  id: number,
  data: InvoiceIssueCreditNoteData
): Promise<void> {
  const body = await fetchInvoiceViaApi(request, id);

  const response = await request.post('/v1/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}/${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()}`);
  }
}
