import type {
  BookedSlotDTO,
  BookedSlotView,
  RequestSlotsParams,
  SlotBookingRequest,
  SlotDTO,
} from '../model/slotTypes'
import { apiFetch } from './apiFetch'

// slots-service через gateway: /api/pcp-slots/** → StripPrefix=2 → slots-service /api/slots/**
const BASE = '/api/pcp-slots/api/slots'

async function readErrorMessage(response: Response, fallback: string): Promise<string> {
  try {
    const text = await response.text()
    if (!text) return `${fallback}: ${response.status}`
    try {
      const body = JSON.parse(text) as { error?: string; message?: string }
      return body.error || body.message || text
    } catch {
      return text
    }
  } catch {
    return `${fallback}: ${response.status}`
  }
}

/**
 * Рассчитывает слоты покрытия заявки (slots-service: GET /api/slots/request-cover).
 * @param params Заявка, интервал и фильтры (ветвь, признак покрытия, КА, угол Солнца,
 *   стратегия автоформирования покрытия).
 * @param signal Сигнал отмены (AbortController) — прерывает долгий расчёт.
 *
 * @returns Список слотов-кандидатов с контурами для карты. При заданной coverageStrategy —
 *   уже отобранный достаточный набор покрытия (преднабор для брони).
 */
export async function fetchRequestSlots(
  params: RequestSlotsParams,
  signal?: AbortSignal,
): Promise<SlotDTO[]> {
  const query = new URLSearchParams()
  query.set('requestId', params.requestId)
  query.set('timeStart', params.timeStart)
  query.set('timeStop', params.timeStop)
  if (params.revSign) query.set('revSign', params.revSign)
  if (params.cov != null) query.set('cov', String(params.cov))
  if (params.sun != null) query.set('sun', String(params.sun))
  if (params.coverageStrategy) query.set('coverageStrategy', params.coverageStrategy)
  if (params.all != null) query.set('all', String(params.all))
  params.satellites?.forEach((id) => query.append('satellites', String(id)))

  const response = await apiFetch(`${BASE}/request-cover?${query}`, {
    headers: { Accept: 'application/json' },
    signal,
  })

  if (!response.ok) {
    throw new Error(await readErrorMessage(response, 'Не удалось рассчитать слоты'))
  }

  return response.json() as Promise<SlotDTO[]>
}

/**
 * Бронирует выбранные слоты под заявку (slots-service: POST /api/slots/booking/request).
 * @param requestId Идентификатор заявки.
 * @param slots Список слотов (КА, номер слота, цикл).
 *
 * @returns Забронированные слоты.
 */
export async function bookRequestSlots(
  requestId: string,
  slots: SlotBookingRequest[],
): Promise<BookedSlotDTO[]> {
  const response = await apiFetch(`${BASE}/booking/request`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({ requestId, slots }),
  })

  if (!response.ok) {
    throw new Error(await readErrorMessage(response, 'Не удалось забронировать слоты'))
  }

  return response.json() as Promise<BookedSlotDTO[]>
}

/**
 * Снимает выбранные брони заявки по списку bookedSlotId (slots-service: DELETE
 * /api/slots/booking/batch). Один запрос = один чанк; крупный набор чанкует вызывающий
 * (runChunked), как при бронировании. Список id передаётся в query (?ids=1&ids=2), а не в теле —
 * тело у DELETE через gateway ненадёжно. requestId обязателен: снятие request-aware, чтобы
 * шаренный между заявками слот не сносился у других.
 * @param requestId Идентификатор заявки.
 * @param bookedSlotIds Идентификаторы броней (bookedSlotId) одного чанка.
 *
 * @returns Сколько броней снято в этом чанке.
 */
export async function cancelBookedSlots(
  requestId: string,
  bookedSlotIds: number[],
): Promise<number> {
  if (bookedSlotIds.length === 0) return 0
  const query = new URLSearchParams()
  query.set('requestId', requestId)
  bookedSlotIds.forEach((id) => query.append('ids', String(id)))

  const response = await apiFetch(`${BASE}/booking/batch?${query}`, {
    method: 'DELETE',
    headers: { Accept: 'application/json' },
  })

  if (!response.ok) {
    throw new Error(await readErrorMessage(response, 'Не удалось снять бронь'))
  }

  const value = Number(await response.text())
  return Number.isFinite(value) ? value : 0
}

/**
 * Загружает уже забронированные слоты заявки (slots-service: GET /api/slots/booking/by-request/{id}).
 * @param requestId Идентификатор заявки.
 *
 * @returns Список забронированных слотов с контурами и bookedSlotId (для снятия по id);
 *   cycle здесь — цикл брони, поэтому slotKey совпадает с ответом POST booking/request.
 */
export async function fetchBookedSlotsByRequest(requestId: string): Promise<BookedSlotView[]> {
  const response = await apiFetch(
    `${BASE}/booking/by-request/${encodeURIComponent(requestId)}`,
    { headers: { Accept: 'application/json' } },
  )

  if (!response.ok) {
    throw new Error(await readErrorMessage(response, 'Не удалось загрузить брони заявки'))
  }

  return response.json() as Promise<BookedSlotView[]>
}
