import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import { URL_CONSTANTS } from "@/constants/URLS";
import type {
  BatchBoardSchedule,
  BatchBoardScheduleDetail,
  BookableSchedule,
  AssignBookingsPayload,
  CompositionRemovalEntry,
  UnassignedBookingsResponse,
  CreateTrainSchedulePayload,
  EligibleContainerBookingsResponse,
  FreightType,
  LocomotiveRecord,
  PinWagonsPayload,
  RecordCheckpointPayload,
  TrainScheduleDetail,
  TrainScheduleFilters,
  TrainScheduleListItem,
  TrainSchedulePreviewPayload,
  TrainSchedulePreviewResponse,
  TrainSchedulingGlobalRules,
  TrainTrackResponse,
  WagonAllocationAttemptResult,
  YardOption,
} from "@/types/trainScheduling";

interface BookingReferenceDataResponse {
  yard?: Array<YardOption & { label?: string }>;
}

const pathsFor = (freightType?: FreightType) =>
  freightType === "BULK"
    ? URL_CONSTANTS.TRAIN_SCHEDULING.BULK
    : URL_CONSTANTS.TRAIN_SCHEDULING.CONTAINER;

export const trainSchedulingService = {
  getEligibleBookings: async (
    filters?: TrainScheduleFilters,
    freightType?: FreightType,
  ): Promise<EligibleContainerBookingsResponse> => {
    const useUnified = !freightType || freightType === "MIXED";
    // The container/bulk endpoints already encode freight type in the path, and their
    // query DTOs reject an extra `freightType` param — so only pass the station filters.
    const response = await client.get<EligibleContainerBookingsResponse>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.ELIGIBLE_BOOKINGS
        : pathsFor(freightType).ELIGIBLE_BOOKINGS,
      { params: { ...filters } },
    );
    return unwrap(response.data);
  },

  preview: async (
    payload: TrainSchedulePreviewPayload,
    freightType?: FreightType,
  ): Promise<TrainSchedulePreviewResponse> => {
    const useUnified = !freightType || freightType === "MIXED";
    const response = await client.post<TrainSchedulePreviewResponse>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW
        : pathsFor(freightType).PREVIEW,
      payload,
    );
    return unwrap(response.data);
  },

  createSchedule: async (
    payload: CreateTrainSchedulePayload,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      pathsFor(freightType).SCHEDULES,
      payload,
    );
    return unwrap(response.data);
  },

  listSchedules: async (
    freightType: FreightType = "CONTAINER",
  ): Promise<TrainScheduleListItem[]> => {
    const response = await client.get<TrainScheduleListItem[]>(
      pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULES,
    );
    return unwrap(response.data);
  },

  getBatchBoard: async (): Promise<BatchBoardSchedule[]> => {
    const response = await client.get<BatchBoardSchedule[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD,
    );
    return unwrap(response.data);
  },

  getBatchBoardDetail: async (
    scheduleId: string,
  ): Promise<BatchBoardScheduleDetail> => {
    const response = await client.get<BatchBoardScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD_DETAIL(scheduleId),
    );
    return unwrap(response.data);
  },

  getBookableSchedules: async (
    originYardId?: string,
    destinationYardId?: string,
  ): Promise<BookableSchedule[]> => {
    const response = await client.get<BookableSchedule[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKABLE_SCHEDULES,
      { params: { originYardId, destinationYardId } },
    );
    return unwrap(response.data);
  },

  /**
   * Day-level pool: the days that have an OPEN departure on the route. Staff pick
   * a day; the batch engine assigns the train. No capacity is returned.
   */
  getAvailableDays: async (
    originYardId?: string,
    destinationYardId?: string,
  ): Promise<string[]> => {
    const response = await client.get<{ days: string[] }>(
      URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS,
      { params: { originYardId, destinationYardId } },
    );
    return unwrap(response.data).days;
  },

  runBatch: async (scheduleId: string): Promise<BatchBoardScheduleDetail> => {
    const response = await client.post<BatchBoardScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.RUN_BATCH(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  runAllocation: async (
    scheduleId: string,
  ): Promise<WagonAllocationAttemptResult> => {
    const response = await client.post<WagonAllocationAttemptResult>(
      URL_CONSTANTS.TRAIN_SCHEDULING.RUN_ALLOCATION(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  setBookingWindow: async (
    scheduleId: string,
    status: "OPEN" | "CLOSED",
  ): Promise<TrainScheduleDetail> => {
    const response = await client.patch<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOW(scheduleId),
      { status },
    );
    return unwrap(response.data);
  },

  markBookingPaid: async (bookingId: string): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId),
      {},
    );
  },

  expireBooking: async (bookingId: string): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId),
      {},
    );
  },

  moveBookingSchedule: async (
    bookingId: string,
    trainScheduleId: string,
  ): Promise<void> => {
    await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId),
      {
        trainScheduleId,
      },
    );
  },

  getScheduleById: async (
    id: string,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.get<TrainScheduleDetail>(
      pathsFor(
        freightType === "MIXED" ? undefined : freightType,
      ).SCHEDULE_BY_ID(id),
    );
    return unwrap(response.data);
  },

  assignUnassignedBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_UNASSIGNED_BOOKING(scheduleId),
      { bookingId },
    );
    return unwrap(response.data);
  },

  assignBookings: async (
    scheduleId: string,
    payload: AssignBookingsPayload,
    freightType?: FreightType,
  ): Promise<TrainScheduleDetail> => {
    const useUnified = !freightType || freightType === "MIXED";
    const response = await client.post<TrainScheduleDetail>(
      useUnified
        ? URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_BOOKINGS(scheduleId)
        : pathsFor(freightType).ASSIGN_BOOKINGS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  unassignBooking: async (
    scheduleId: string,
    bookingId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.delete<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGN_BOOKING(scheduleId, bookingId),
    );
    return unwrap(response.data);
  },

  pinWagons: async (
    scheduleId: string,
    payload: PinWagonsPayload,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.PIN_WAGONS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  finalizeSchedule: async (
    scheduleId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.FINALIZE(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  dispatchSchedule: async (
    scheduleId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.DISPATCH(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  getTrack: async (scheduleId: string): Promise<TrainTrackResponse> => {
    const response = await client.get<TrainTrackResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
    );
    return unwrap(response.data);
  },

  recordCheckpoint: async (
    scheduleId: string,
    payload: RecordCheckpointPayload,
  ): Promise<TrainTrackResponse> => {
    const response = await client.post<TrainTrackResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  arriveSchedule: async (scheduleId: string): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.ARRIVE(scheduleId),
      {},
    );
    return unwrap(response.data);
  },

  cancelSchedule: async (
    id: string,
    freightType: FreightType = "CONTAINER",
  ): Promise<TrainScheduleDetail> => {
    const response = await client.post<TrainScheduleDetail>(
      pathsFor(
        freightType === "MIXED" ? undefined : freightType,
      ).CANCEL_SCHEDULE(id),
      {},
    );
    return unwrap(response.data);
  },

  getAvailableLocomotives: async (
    routeId?: string,
  ): Promise<LocomotiveRecord[]> => {
    if (routeId) {
      const response = await client.get<LocomotiveRecord[]>(
        URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_LOCOMOTIVES,
        { params: { routeId } },
      );
      return unwrap(response.data);
    }
    const response = await client.get<LocomotiveRecord[]>(
      URL_CONSTANTS.LOCOMOTIVES.BASE,
      {
        params: { status: "AVAILABLE" },
      },
    );
    return unwrap(response.data);
  },

  previewReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      trigger: string;
      reason?: string;
      newDepartureDate?: string;
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_PREVIEW(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  executeReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      trigger: string;
      reason?: string;
      newDepartureDate?: string;
      finalBookingIds: string[];
      displacedBookingIds: string[];
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_EXECUTE(scheduleId),
      payload,
    );
    return unwrap(response.data);
  },

  maintenanceReschedule: async (
    scheduleId: string,
    payload: {
      incomingBookingIds: string[];
      newDepartureDate: string;
      reason?: string;
    },
  ) => {
    const response = await client.post(
      URL_CONSTANTS.TRAIN_SCHEDULING.MAINTENANCE(scheduleId),
      { ...payload, trigger: "TRAIN_MAINTENANCE" },
    );
    return unwrap(response.data);
  },

  getGlobalRules: async (): Promise<TrainSchedulingGlobalRules> => {
    const response = await client.get<TrainSchedulingGlobalRules>(
      URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
    );
    return unwrap(response.data);
  },

  updateGlobalRules: async (
    payload: Partial<
      Pick<
        TrainSchedulingGlobalRules,
        | "maxTrainLengthMeters"
        | "maxTrainWeightTons"
        | "maxWagonsPerTrain"
        | "max20ftContainerWeightTons"
        | "max20ftPairWeightDiffTons"
      >
    >,
  ): Promise<TrainSchedulingGlobalRules> => {
    const response = await client.patch<TrainSchedulingGlobalRules>(
      URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
      payload,
    );
    return unwrap(response.data);
  },

  getStations: async (): Promise<YardOption[]> => {
    const response = await client.get<BookingReferenceDataResponse>(
      URL_CONSTANTS.BOOKINGS.REFERENCE_DATA,
    );
    const data = unwrap(response.data);
    return (data.yard ?? []).map((yard) => ({
      id: yard.id,
      name: yard.name ?? yard.label ?? yard.code,
      code: yard.code,
      country: yard.country,
    }));
  },

  removeWagonSlot: async (
    scheduleId: string,
    wagonId: string,
  ): Promise<TrainScheduleDetail> => {
    const response = await client.delete<TrainScheduleDetail>(
      URL_CONSTANTS.TRAIN_SCHEDULING.REMOVE_WAGON_SLOT(scheduleId, wagonId),
    );
    return unwrap(response.data);
  },

  updateContainerItem: async (
    scheduleId: string,
    itemId: string,
    payload: { containerNumber: string | null },
  ): Promise<{ id: string; containerNumber: string | null }> => {
    const response = await client.patch<{
      id: string;
      containerNumber: string | null;
    }>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UPDATE_CONTAINER_ITEM(scheduleId, itemId),
      payload,
    );
    return unwrap(response.data);
  },

  getUnassignedBookings: async (
    scheduleId: string,
  ): Promise<UnassignedBookingsResponse> => {
    const response = await client.get<UnassignedBookingsResponse>(
      URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGNED_BOOKINGS(scheduleId),
    );
    return unwrap(response.data);
  },

  getCompositionRemovals: async (
    scheduleId: string,
  ): Promise<CompositionRemovalEntry[]> => {
    const response = await client.get<CompositionRemovalEntry[]>(
      URL_CONSTANTS.TRAIN_SCHEDULING.COMPOSITION_REMOVALS(scheduleId),
    );
    return unwrap(response.data);
  },
};
