import type { Freight, PaginatedResponse } from "@edr/types";

import { URL_CONSTANTS } from "@/constants/URLS";
import type { BookingDocuments } from "@/pages/bookings/new-booking-form/schema";
import { buildBookingFormData } from "./booking-form-data";
import { client } from "../utils/api";

const B = URL_CONSTANTS.BOOKINGS;

export interface MileVehicleSummary {
  plate: string | null;
  code: string | null;
  driverName: string | null;
  containerNumber: string | null;
  distanceKm: number | null;
}
export interface MileLegSummary {
  status: string;
  exactKm: number | null;
  remainingPayment: number | null;
  currency: string;
  invoiced: boolean;
  vehicles: MileVehicleSummary[];
}
export interface MileSummaryResponse {
  firstMile: MileLegSummary | null;
  lastMile: MileLegSummary | null;
}

export type CreateBookingPayload = Freight.CreateBookingDto;

export interface ContractView {
  bookingId: string;
  reference: string;
  status: string;
  templateKey: string;
  title: string;
  html: string;
  canSignCustomer: boolean;
  canSignStaff: boolean;
  hasContractDocument: boolean;
  signatures: Array<{
    role: string;
    signerDisplayName: string;
    signedAt: string;
    signatureImageUrl?: string | null;
    stampImageUrl?: string | null;
  }>;
  /** Current viewer's reusable saved signature, if they have one. */
  savedSignature?: {
    signerDisplayName: string;
    signatureImageUrl?: string | null;
  } | null;
}

export interface PriceLineItem {
  code: string;
  description: string;
  /** Computed line total (unitAmount × quantity). */
  amount: number;
  /** Price for a single unit of this charge (e.g. one 20ft container, one ton). */
  unitAmount?: number;
  /** Unit the rate is charged per: PER_CONTAINER | PER_TON | PER_WAGON | PER_KM | FLAT. */
  unit?: string;
  /** How many units this charge applies to (containers, tons, wagons; 1 for FLAT). */
  quantity?: number;
  currency: string;
}

/**
 * An announced upcoming/open booking window, shown to every signed-in customer
 * regardless of contract. Import trains open a window on one booking day;
 * export trains open 24h before departure (first come, first served).
 */
export interface MyBookingWindow {
  scheduleId: string;
  /** Train schedule reference (e.g. TS-2026-000123), shown on the window card. */
  reference: string | null;
  /**
   * The customer's active contract on this lane, when they hold one — enables
   * "Book now" to target it. Null for lanes they have no contract on.
   */
  contractId: string | null;
  /** ONE_TIME contracts can't draw down against a window — button is hidden. */
  contractKind: "ONE_TIME" | "GENERAL" | null;
  direction: "IMPORT" | "EXPORT" | null;
  windowPhase: string | null;
  isOpenNow: boolean;
  windowOpensAt: string | null;
  windowClosesAt: string | null;
  docReviewEndsAt: string | null;
  paymentPhaseEndsAt: string | null;
  bookingWindowStatus: string;
  bookingCycleNo: number;
  departureDate: string;
  origin: string | null;
  destination: string | null;
  /**
   * Full ordered corridor for the window's route — origin, every intermediate
   * milestone stop, then destination (e.g. Djibouti → Adama → Dire Dawa).
   * Falls back to [origin, destination] when the route has no milestones.
   */
  routeStations: string[];
}

export interface GeneratePriceResponse {
  bookingId: string;
  totalAmount: number;
  currency: string;
  lineItems: PriceLineItem[];
  warnings: string[];
}

export interface SubmitBookingResponse {
  bookingId: string;
  status: string;
  priceChanged: boolean;
  previousTotalAmount?: number;
  totalAmount: number;
  currency: string;
  lineItems?: PriceLineItem[];
  message?: string;
}

export interface SignContractPayload {
  role: "CUSTOMER" | "STAFF";
  signatureImageBase64: string;
  /** Company stamp/seal image; required to sign a contract (not booking contracts). */
  stampImageBase64?: string;
  signerDisplayName: string;
  consentText?: string;
  /** Sudo-mode OTP challenge; required when role=CUSTOMER. */
  otp?: string;
}

export interface ApproveDeliveryResponse {
  bookingId: string;
  inventoryId: string;
  approvedAt: string;
  signerDisplayName: string;
}

/** One import handover record — booking-level or per truck (EDR last-mile). */
export interface BookingHandoverRecord {
  id: string;
  reference: string;
  truckPlate: string | null;
  mileType: "SELF_HAUL" | "EDR_LAST_MILE";
  generatedAt: string;
  signedAt: string | null;
  signerName: string | null;
  deliveredAt: string | null;
}

export interface SignHandoverResponse {
  handoverId: string;
  bookingId: string;
  signedAt: string | null;
  signerDisplayName: string;
  allSigned: boolean;
}

export interface CustomerTruckAssignmentPayload {
  truckPlateNumber: string;
  driverName: string;
  truckType: string;
  containerNumberToLoad: string;
}

export interface BookingListFilter {
  status?: string;
  /** Comma-separated statuses (overrides `status` when set). */
  statuses?: string;
  /** ONE_TIME or GENERAL_CONTRACT. */
  bookingType?: string;
  /** CONTAINER or BULK. */
  freightType?: string;
  /** IMPORT / EXPORT / DOMESTIC. */
  tradeDirection?: string;
  /** Narrow to a single operational profile (importer/exporter/freight_forwarder). */
  companyProfileId?: string;
  /** Created-date range (ISO). */
  createdFrom?: string;
  createdTo?: string;
  /** Free-text search: booking reference, company name, contract reference (server-side). */
  search?: string;
  page?: number;
  pageSize?: number;
  sortBy?: string;
  sortOrder?: "ASC" | "DESC";
}

export const bookingsService = {
  list: async (
    filter: BookingListFilter | void = {},
  ): Promise<PaginatedResponse<Freight.IBooking>> => {
    const { data } = await client.get("/api/bookings", { params: filter });
    return data.data;
  },
  get: async (id: string): Promise<Freight.IBooking> => {
    const { data } = await client.get(`/api/bookings/${id}`);
    return data.data;
  },
  mileSummary: async (id: string): Promise<MileSummaryResponse> => {
    const { data } = await client.get(`/api/bookings/${id}/mile-summary`);
    return data.data;
  },
  assignCustomerTruck: async (
    id: string,
    payload: CustomerTruckAssignmentPayload,
  ): Promise<Freight.IBooking> => {
    const { data } = await client.post(
      `/api/bookings/${id}/customer-truck-assignment`,
      payload,
    );
    return data.data;
  },
  downloadCustomerTruckFreightOrder: async (id: string, copies?: number[]): Promise<Blob> => {
    const { data } = await client.get(
      `/api/bookings/${id}/customer-truck-assignment/freight-order`,
      { responseType: "blob", params: copies?.length ? { copies: copies.join(",") } : undefined },
    );
    return data;
  },
  downloadHandoverDocument: async (inventoryId: string): Promise<Blob> => {
    const { data } = await client.get(
      `/api/warehouse-inventory/${inventoryId}/handover-document`,
      { responseType: "blob" },
    );
    return data;
  },
  downloadBookingHandoverDocument: async (
    bookingId: string,
    handoverId?: string,
  ): Promise<Blob> => {
    const { data } = await client.get(
      `/api/warehouse-inventory/bookings/${bookingId}/handover-document`,
      { responseType: "blob", params: handoverId ? { handoverId } : undefined },
    );
    return data;
  },

  listBookingHandovers: async (
    bookingId: string,
  ): Promise<BookingHandoverRecord[]> => {
    const { data } = await client.get(
      `/api/warehouse-inventory/bookings/${bookingId}/handovers`,
    );
    return data.data ?? data;
  },

  signHandover: async (
    handoverId: string,
    signerName: string,
  ): Promise<SignHandoverResponse> => {
    const { data } = await client.post(
      `/api/warehouse-inventory/handovers/${handoverId}/sign`,
      { signerName },
    );
    return data.data ?? data;
  },
  downloadBookingGrnDocument: async (bookingId: string): Promise<Blob> => {
    const { data } = await client.get(
      `/api/warehouse-inventory/bookings/${bookingId}/grn-document`,
      { responseType: "blob" },
    );
    return data;
  },
  downloadBookingReleaseDocument: async (bookingId: string): Promise<Blob> => {
    const { data } = await client.get(
      `/api/warehouse-inventory/bookings/${bookingId}/release-document`,
      { responseType: "blob" },
    );
    return data;
  },
  tracking: async (id: string): Promise<Freight.IBookingTracking> => {
    const { data } = await client.get(`/api/bookings/${id}/tracking`);
    return data.data;
  },
  create: async (
    payload: CreateBookingPayload,
    documents?: BookingDocuments,
  ): Promise<Freight.IBooking> => {
    const formData = buildBookingFormData(payload, documents);
    const { data } = await client.post("/api/bookings", formData, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    return data.data.booking;
  },
  getReferenceData: async (): Promise<Freight.BookingReferenceData> => {
    const { data } = await client.get("/api/bookings/reference-data");
    return data.data;
  },
  update: async (
    id: string,
    payload: Partial<CreateBookingPayload>,
    documents?: BookingDocuments,
  ): Promise<{ booking: Freight.IBooking; warnings: string[] }> => {
    const formData = buildBookingFormData(payload, documents);
    const { data } = await client.patch(`/api/bookings/${id}`, formData, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    return data.data;
  },

  remove: async (id: string): Promise<void> => {
    await client.delete(`/api/bookings/${id}`);
  },

  cancel: async (id: string, reason: string): Promise<Freight.IBooking> => {
    const { data } = await client.post(`/api/bookings/${id}/cancel`, { reason });
    return data.data;
  },

  reject: async (id: string, reason?: string): Promise<Freight.IBooking> => {
    const { data } = await client.post(`/api/bookings/${id}/reject`, { reason });
    return data.data;
  },

  generatePrice: async (id: string): Promise<GeneratePriceResponse> => {
    const { data } = await client.post(`/api/bookings/${id}/generate-price`);
    return data.data;
  },

  submit: async (id: string): Promise<SubmitBookingResponse> => {
    const { data } = await client.post(`/api/bookings/${id}/submit`);
    return data.data;
  },

  confirmSubmit: async (id: string): Promise<SubmitBookingResponse> => {
    const { data } = await client.post(`/api/bookings/${id}/confirm-submit`);
    return data.data;
  },

  uploadDocuments: async (
    id: string,
    files: Record<string, File | File[] | null>,
  ): Promise<Freight.IBooking> => {
    const formData = new FormData();
    for (const [key, fileOrFiles] of Object.entries(files)) {
      if (!fileOrFiles) continue;
      if (Array.isArray(fileOrFiles)) {
        for (const f of fileOrFiles) formData.append(key, f);
      } else {
        formData.append(key, fileOrFiles);
      }
    }
    const { data } = await client.post(
      `/api/bookings/${id}/documents`,
      formData,
      { headers: { "Content-Type": "multipart/form-data" } },
    );
    return data.data;
  },

  // ── Document clearance ──
  getClearance: async (id: string): Promise<Freight.ClearanceView> => {
    const { data } = await client.get(`/api/bookings/${id}/clearance`);
    return data.data ?? data;
  },

  submitClearanceDocuments: async (
    id: string,
    files: Record<string, File | null>,
  ): Promise<Freight.IBooking> => {
    const formData = new FormData();
    for (const [key, file] of Object.entries(files)) {
      if (file) formData.append(key, file);
    }
    const { data } = await client.post(
      `/api/bookings/${id}/clearance/documents`,
      formData,
      { headers: { "Content-Type": "multipart/form-data" } },
    );
    return data.data;
  },

  proceedToOperation: async (
    id: string,
    scheduledDate: string,
  ): Promise<Freight.IBooking> => {
    const { data } = await client.post(
      `/api/bookings/${id}/clearance/proceed`,
      { scheduledDate },
    );
    return data.data;
  },

  uploadBookingClearanceDutySlip: async (
    id: string,
    file: File,
  ): Promise<Freight.IBooking> => {
    const form = new FormData();
    form.append("file", file);
    const { data } = await client.post(`/api/bookings/${id}/clearance/duty-slip`, form, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    return data.data ?? data;
  },

  getContractView: async (id: string): Promise<ContractView> => {
    const { data } = await client.get(B.CONTRACT_VIEW(id));
    return data.data ?? data;
  },

  downloadContractDocument: async (id: string): Promise<Blob> => {
    const { data } = await client.get(B.CONTRACT_DOCUMENT(id), {
      responseType: "blob",
    });
    return data;
  },

  checkPayment: async (orderId: string): Promise<{ status: string }> => {
    const { data } = await client.post(`/api/payments/bookings/check-payment/${orderId}`);
    return data.data ?? data;
  },

  signContract: async (
    id: string,
    payload: SignContractPayload,
  ): Promise<Freight.IBooking> => {
    const { data } = await client.post(B.CONTRACT_SIGN(id), payload);
    return data.data ?? data;
  },

  approveDelivery: async (
    id: string,
    signerName: string,
  ): Promise<ApproveDeliveryResponse> => {
    const { data } = await client.post(
      `/api/warehouse-inventory/bookings/${id}/approve-delivery`,
      { signerName },
    );
    return data.data ?? data;
  },

  getBookableSchedules: async (
    query: Freight.BookableSchedulesQuery = {},
  ): Promise<Freight.BookableScheduleItem[]> => {
    const { data } = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.BOOKABLE_SCHEDULES,
      { params: query },
    );
    return data.data;
  },

  /**
   * Day-level pool: the days that have a departure on the route. The customer
   * picks a day; the engine assigns the train. No capacity is returned.
   */
  getAvailableDays: async (
    query: Freight.AvailableDaysQuery = {},
  ): Promise<string[]> => {
    const { data } = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS,
      { params: query },
    );
    return (data.data as Freight.AvailableDaysResponse).days;
  },

  // Cargo-aware day pool: only days where a train has remaining capacity AND a
  // wagon TYPE that can carry this cargo. `containers`/`containerTypeIds` are
  // serialized as JSON string params (the server parses them). Days only — no
  // capacity counts are ever returned.
  getAvailableDaysForCargo: async (
    query: Freight.AvailableDaysForCargoQuery,
  ): Promise<string[]> => {
    const { containers, containerTypeIds, ...rest } = query;
    const { data } = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS_FOR_CARGO,
      {
        params: {
          ...rest,
          ...(containers ? { containers: JSON.stringify(containers) } : {}),
          ...(containerTypeIds?.length
            ? { containerTypeIds: JSON.stringify(containerTypeIds) }
            : {}),
        },
      },
    );
    return (data.data as Freight.AvailableDaysResponse).days;
  },

  // Days bookable for an EXISTING booking (operation-request step): the server
  // derives the cargo from the booking and applies the wagon-type gate.
  getAvailableDaysForBooking: async (bookingId: string): Promise<string[]> => {
    const { data } = await client.get(
      `/api/bookings/${bookingId}/available-days`,
    );
    return (data.data as Freight.AvailableDaysResponse).days;
  },

  // Advisory free-wagon count for a shipment day (planning hint, not enforced).
  getDayAvailability: async (
    bookingId: string,
    date: string,
  ): Promise<Freight.DayAvailabilityResponse> => {
    const { data } = await client.get(
      `/api/bookings/${bookingId}/day-availability`,
      { params: { date } },
    );
    return data.data as Freight.DayAvailabilityResponse;
  },

  /**
   * Upcoming/open booking windows on the signed-in customer's active-contract
   * lanes (import booking-day windows + export 24h pre-departure windows).
   */
  getMyBookingWindows: async (): Promise<MyBookingWindow[]> => {
    const { data } = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.MY_BOOKING_WINDOWS,
    );
    return data.data ?? data;
  },

  /**
   * Booking windows for a single contract's routes (same row shape as
   * `getMyBookingWindows`). Used to gate the direct "New shipment booking"
   * entry on the contract detail page and the new-shipment form.
   */
  getContractBookingWindows: async (
    contractId: string,
  ): Promise<MyBookingWindow[]> => {
    const { data } = await client.get(
      URL_CONSTANTS.TRAIN_SCHEDULING.CONTRACT_BOOKING_WINDOWS(contractId),
    );
    return data.data ?? data;
  },
};
