import {
  fromBookingCreateApiResponse,
  toBookingAdjustmentApiPayload,
  toBookingCreateApiPayload,
  toBookingPriceChangeApiPayload,
} from '@/manager/modules/booking/booking-mappers';
import {
  BookingAdjustmentData,
  BookingCreateApiResponse,
  BookingCreateData,
  BookingPriceChange,
  BookingShowApiResponse,
} from '@/manager/modules/booking/booking-types';
import { Booking } from '@/shared/types/booking-types';
import { isModernBillingMode } from '@/shared/utils/billing-utils';
import { APIRequestContext } from '@playwright/test';

// The manager API serves bookings at /v1/booking; the billing API (modern Kinnovis billing) serves them at
// /api/v1/booking. A BookingApiClient bundles the request context with its matching base path so create and
// cancel hit the right host without each call re-deriving the mode.
export interface BookingApiClient {
  request: APIRequestContext;
  basePath: string;
}

const managerBookingBasePath = '/v1/booking';
const billingBookingBasePath = '/api/v1/booking';

export function resolveBookingApiClient(
  managerApiRequest: APIRequestContext,
  billingApiRequest: APIRequestContext
): BookingApiClient {
  return isModernBillingMode()
    ? { request: billingApiRequest, basePath: billingBookingBasePath }
    : { request: managerApiRequest, basePath: managerBookingBasePath };
}

export async function createBookingViaApi(client: BookingApiClient, data: BookingCreateData): Promise<Booking> {
  const response = await client.request.post(client.basePath, {
    data: toBookingCreateApiPayload(data),
  });

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

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

  return fromBookingCreateApiResponse(body, data);
}

export async function createBookingPriceChangeViaApi(
  client: BookingApiClient,
  bookingIds: number[],
  priceChange: BookingPriceChange
): Promise<void> {
  const response = await client.request.post(`${client.basePath}/price-change`, {
    data: toBookingPriceChangeApiPayload(priceChange, bookingIds),
  });

  if (!response.ok()) {
    throw new Error(
      `Failed to create price change for bookings ${bookingIds} via API: ` +
        `${response.status()} ${await response.text()}`
    );
  }
}

export async function createBookingAdjustmentViaApi(
  client: BookingApiClient,
  booking: Booking,
  adjustment: BookingAdjustmentData
): Promise<void> {
  const bookingUpdatedAt = await getBookingUpdatedAtViaApi(client, booking.id);

  const response = await client.request.post(`${client.basePath}/${booking.id}/adjustment`, {
    data: toBookingAdjustmentApiPayload(booking, adjustment, bookingUpdatedAt),
  });

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

async function getBookingUpdatedAtViaApi(client: BookingApiClient, id: number): Promise<string> {
  const response = await client.request.get(`${client.basePath}/${id}`);

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

  const body = (await response.json()) as BookingShowApiResponse;
  const updatedAt = body.data.updated_at ?? body.data.updatedAt;

  if (updatedAt == null) {
    throw new Error(`Booking ${id} response contains neither updated_at nor updatedAt`);
  }

  return updatedAt;
}

export async function cancelBookingAtPeriodEndViaApi(client: BookingApiClient, id: number): Promise<void> {
  const response = await client.request.put(`${client.basePath}/${id}/cancel`, {
    data: { type: 'end_of_period', cancel_on_date: null },
  });

  if (!response.ok()) {
    throw new Error(
      `Failed to cancel booking ${id} at period end via API: ${response.status()} ${await response.text()}`
    );
  }
}

export async function cancelBookingNowViaApi(client: BookingApiClient, id: number): Promise<void> {
  const body = { data: { type: 'now', cancel_on_date: null } };

  const cancel = await client.request.put(`${client.basePath}/${id}/cancel`, body);

  if (cancel.ok() || cancel.status() === 404) {
    return;
  }

  const postpone = await client.request.put(`${client.basePath}/${id}/cancel-postpone`, body);

  if (postpone.ok() || postpone.status() === 404) {
    return;
  }

  throw new Error(
    `Failed to cancel booking ${id} via API: cancel ${cancel.status()}, ` +
      `postpone ${postpone.status()} ${await postpone.text()}`
  );
}
