import { TguApiError } from '../model/tguTypes'
import { apiFetch } from './apiFetch'

const BASE = '/api/pcp-mission'

/** Способ построения окна маршрута относительно момента видимости. */
export type SurveyRouteAnchor = 'CENTERED' | 'FORWARD'

export type BuildSurveyRouteParams = {
  /** Идентификатор КА (NORAD — тот же, что в obj-view/mpl-point). */
  satelliteId: number
  /** Момент видимости (UTC ISO). */
  time: string
  /** Длительность съёмки, секунды. */
  durationSeconds: number
  /** Крен (gamma), градусы. */
  roll: number
  /** Угол захвата (полуширина полосы), градусы. */
  captureAngle: number
  /** Номер витка (0 — определить по орбитальным точкам). */
  revolution?: number
  /** Способ построения окна относительно момента видимости. */
  anchor?: SurveyRouteAnchor
  /**
   * Начало интервала плана (UTC ISO). Передаётся при сохранении,
   * чтобы при автосоздании миссии/плана задать корректное окно (для пересечений/наложений).
   */
  planStart?: string
  /** Конец интервала плана (UTC ISO). См. planStart. */
  planEnd?: string
}

export type SurveyRoute = {
  satelliteId: number
  /** Начало съёмки (UTC ISO). */
  timeStart: string
  /** Длительность съёмки, секунды. */
  duration: number
  revolution: number
  /** Крен (gamma), градусы. */
  roll: number
  /** Угол захвата, градусы. */
  captureAngle: number
  /** Широта центра контура, градусы. */
  lat: number
  /** Долгота центра контура, градусы. */
  longitude: number
  /** Контур полосы съёмки, WKT POLYGON (lon lat, ...). */
  contourWkt: string
  /** Объём маршрута, МБ (скорость накопления × длительность). Считает бэк (pcp-types-lib). */
  volumeMb?: number
  /** Идентификатор сохранённого режима съёмки в БД (есть только после сохранения). */
  surveyModeId?: number
}

/**
 * Построение одного маршрута съёмки по параметрам видимости точки без сохранения
 * (pcp-mission-planing-service, POST /api/missions/survey-route/build). Live-превью.
 * @param params Параметры окна съёмки (КА, момент, длительность, крен, угол захвата).
 *
 * @returns Построенный маршрут съёмки (контур, объём) без сохранения.
 */
export async function buildSurveyRoute(params: BuildSurveyRouteParams): Promise<SurveyRoute> {
  return postSurveyRoute(`${BASE}/api/missions/survey-route/build`, params)
}

/**
 * Построение и сохранение маршрута съёмки в БД по идентификатору выбранного плана
 * (pcp-mission-planing-service, POST /api/missions/{planId}/survey-route).
 * planId — идентификатор выбранного плана в pcp-tgu-service (= mission_id).
 * @param planId Идентификатор выбранного плана в pcp-tgu-service (= mission_id).
 * @param params Параметры окна съёмки.
 *
 * @returns Сохранённый маршрут съёмки (с surveyModeId).
 */
export async function saveSurveyRoute(
  planId: string,
  params: BuildSurveyRouteParams,
): Promise<SurveyRoute> {
  return postSurveyRoute(`${BASE}/api/missions/${encodeURIComponent(planId)}/survey-route`, params)
}

export type SaveDropRouteParams = {
  /** Идентификатор КА (NORAD). */
  satelliteId: number
  /** Идентификатор/номер станции приёма (из ЗРВ). */
  station: string
  /** Номер витка зоны радиовидимости. */
  revolution: number
  /** Начало зоны (UTC ISO). */
  zoneStart: string
  /** Конец зоны (UTC ISO). */
  zoneStop: string
  /** Идентификаторы режимов съёмки (survey_modes), сбрасываемых в этой зоне. */
  surveyModeIds: number[]
  /** Начало интервала плана (для автосоздания миссии/плана). */
  planStart?: string
  /** Конец интервала плана. */
  planEnd?: string
}

/** Сохранённый режим сброса (ответ /drop-route). */
export type DropMode = {
  id: number
  planId: number
  timeStart: string | null
  revolution: number
  type: 'DROP'
  station: string | null
  duration: number
  surveys: number[]
  /** Объём сброса, Мбайт (канал 1200 Мбит/с ÷ 8 × длительность). Считает бэк (pcp-types-lib). */
  volumeMb?: number
}

/**
 * Ручное добавление сброса по выбранной зоне радиовидимости
 * (pcp-mission-planing-service, POST /api/missions/{planId}/drop-route).
 * planId — идентификатор выбранного плана в pcp-tgu-service (= mission_id).
 * @param planId Идентификатор выбранного плана в pcp-tgu-service (= mission_id).
 * @param params Параметры сброса (КА, станция, зона радиовидимости, режимы съёмки).
 *
 * @returns Сохранённый режим сброса.
 */
export async function saveDropRoute(
  planId: string,
  params: SaveDropRouteParams,
): Promise<DropMode> {
  const body = {
    satelliteId: params.satelliteId,
    station: params.station,
    revolution: params.revolution,
    zoneStart: params.zoneStart,
    zoneStop: params.zoneStop,
    surveyModeIds: params.surveyModeIds,
    ...(params.planStart ? { planStart: params.planStart } : {}),
    ...(params.planEnd ? { planEnd: params.planEnd } : {}),
  }

  const url = `${BASE}/api/missions/${encodeURIComponent(planId)}/drop-route`
  let response: Response
  try {
    response = await apiFetch(url, {
      method: 'POST',
      headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }

  const text = await response.text()
  if (!response.ok) {
    throw new TguApiError(
      extractErrorText(text) || 'Ошибка сохранения сброса.',
      'http',
      response.status,
    )
  }
  if (!text.trim()) {
    throw new TguApiError(
      'Сервис планирования миссий вернул пустой ответ.',
      'http',
      response.status,
    )
  }

  return JSON.parse(text) as DropMode
}

/** Итог расчёта плана съёмки (ответ /surveys/calculate). Времена приходят из API как UTC ISO и отображаются UI в локальной зоне браузера. */
export type SurveyCalculationResult = {
  missionId: string
  completed: boolean
  comPlanSnapshotId: number | null
  surveyModesCount: number
  manualModesCount: number
  complanModesCount: number
  slotsModesCount: number
  mixedModesCount: number
  bookedSlotIdsCount: number
}

/**
 * Запуск расчёта плана съёмки для плана-черновика
 * (pcp-mission-planing-service, POST /api/missions/{planId}/surveys/calculate).
 * Источник комплексного плана (snapshot этапа 4) бэкенд выбирает сам по интервалу плана.
 * Пересчёт сохраняет ручные маршруты (source=MANUAL), затирая только пересчитываемые.
 * @param planId Идентификатор плана-черновика (= mission_id).
 *
 * @returns Итог расчёта плана съёмки (счётчики режимов, completed).
 */
export async function calculateSurveys(planId: string): Promise<SurveyCalculationResult> {
  const url = `${BASE}/api/missions/${encodeURIComponent(planId)}/surveys/calculate`
  let response: Response
  try {
    response = await apiFetch(url, { method: 'POST', headers: { Accept: 'application/json' } })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }
  const text = await response.text()
  if (!response.ok) {
    throw new TguApiError(
      extractErrorText(text) || 'Не удалось рассчитать съёмку.',
      'http',
      response.status,
    )
  }
  return JSON.parse(text) as SurveyCalculationResult
}

/**
 * Запуск расчёта плана сбросов для плана-черновика по уже рассчитанным/добавленным съёмкам
 * (pcp-mission-planing-service, POST /api/missions/{planId}/drops/calculate). Возвращает 204.
 * @param planId Идентификатор плана-черновика (= mission_id).
 */
export async function calculateDrops(planId: string): Promise<void> {
  const url = `${BASE}/api/missions/${encodeURIComponent(planId)}/drops/calculate`
  let response: Response
  try {
    response = await apiFetch(url, { method: 'POST', headers: { Accept: 'application/json' } })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }
  if (!response.ok) {
    const text = await response.text()
    throw new TguApiError(
      extractErrorText(text) || 'Не удалось рассчитать сбросы.',
      'http',
      response.status,
    )
  }
}

/**
 * Очистка плана-черновика (pcp-mission-planing-service, DELETE /api/missions/{planId}/modes).
 * Сбросы удаляются всегда; `keepManual=true` сохраняет ручные маршруты съёмки (source=MANUAL),
 * `keepManual=false` (по умолчанию) удаляет все съёмки, включая ручные. Возвращает 204.
 * @param planId Идентификатор плана-черновика (= mission_id).
 * @param keepManual Сохранить ручные маршруты съёмки (source=MANUAL); по умолчанию удалять всё.
 */
export async function clearPlanModes(planId: string, keepManual = false): Promise<void> {
  const query = keepManual ? '?keepManual=true' : ''
  const url = `${BASE}/api/missions/${encodeURIComponent(planId)}/modes${query}`
  let response: Response
  try {
    response = await apiFetch(url, { method: 'DELETE', headers: { Accept: 'application/json' } })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }
  if (!response.ok) {
    const text = await response.text()
    throw new TguApiError(
      extractErrorText(text) || 'Не удалось очистить план.',
      'http',
      response.status,
    )
  }
}

/**
 * Перестроение контура существующего режима съёмки по новым параметрам окна/крена
 * (pcp-mission-planing-service, PATCH /api/missions/survey-modes/{modeId}). Правка включения
 * в плане-черновике: контур пересчитывается тем же алгоритмом, что и /survey-route/build.
 * @param modeId Идентификатор существующего режима съёмки (survey_modes).
 * @param params Новые параметры окна/крена для перестроения контура.
 *
 * @returns Перестроенный маршрут съёмки.
 */
export async function updateSurveyRoute(
  modeId: number,
  params: BuildSurveyRouteParams,
): Promise<SurveyRoute> {
  return postSurveyRoute(
    `${BASE}/api/missions/survey-modes/${encodeURIComponent(modeId)}`,
    params,
    'PATCH',
  )
}

/**
 * Удаление включения (съёмки или сброса) из плана-черновика
 * (pcp-mission-planing-service, DELETE /api/missions/modes/{modeId}). Для съёмки бэк снимает
 * брони слотов и связи со сбросами, осиротевший сброс удаляется; для сброса — только его связи.
 * @param modeId Идентификатор включения (режима съёмки или сброса).
 */
export async function deleteMode(modeId: number): Promise<void> {
  const url = `${BASE}/api/missions/modes/${encodeURIComponent(modeId)}`
  let response: Response
  try {
    response = await apiFetch(url, { method: 'DELETE', headers: { Accept: 'application/json' } })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }
  if (!response.ok) {
    const text = await response.text()
    throw new TguApiError(
      extractErrorText(text) || 'Ошибка удаления включения.',
      'http',
      response.status,
    )
  }
}

/** Уровень конфликта валидации: error блокирует «Применить», warning — информативный. */
export type ConflictSeverity = 'error' | 'warning'

/** Один конфликт на затронутом включении (дифф по режимам). */
export type ModeConflict = {
  /** id режима (survey или drop), к которому относится конфликт. */
  modeId: number
  /** Код правила (TIME_OVERLAP / TARGET_VISIBILITY / COMMS_SESSION / DROP_ORDER / ONBOARD_LIMIT). */
  rule: string
  severity: ConflictSeverity
  message: string
  /** id второго участника парного правила (например, пересечения по времени), если есть. */
  relatedModeId?: number | null
}

export type MissionValidation = {
  missionId: string
  conflicts: ModeConflict[]
}

/**
 * Валидация миссии-черновика на конфликты/наложения включений
 * (pcp-mission-planing-service, POST /api/missions/{id}/validate). MVP: бэк-заглушка возвращает
 * корректный контракт с пустым списком конфликтов; контракт стабилен для UI.
 * @param missionId Идентификатор миссии-черновика.
 *
 * @returns Результат валидации со списком конфликтов.
 */
export async function validateMission(missionId: string): Promise<MissionValidation> {
  const url = `${BASE}/api/missions/${encodeURIComponent(missionId)}/validate`
  let response: Response
  try {
    response = await apiFetch(url, { method: 'POST', headers: { Accept: 'application/json' } })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }
  const text = await response.text()
  if (!response.ok) {
    throw new TguApiError(
      extractErrorText(text) || 'Ошибка валидации плана.',
      'http',
      response.status,
    )
  }
  return JSON.parse(text) as MissionValidation
}

async function postSurveyRoute(
  url: string,
  params: BuildSurveyRouteParams,
  method: 'POST' | 'PATCH' = 'POST',
): Promise<SurveyRoute> {
  const body = {
    satelliteId: params.satelliteId,
    time: params.time,
    durationSeconds: params.durationSeconds,
    roll: params.roll,
    captureAngle: params.captureAngle,
    revolution: params.revolution ?? 0,
    anchor: params.anchor ?? 'CENTERED',
    ...(params.planStart ? { planStart: params.planStart } : {}),
    ...(params.planEnd ? { planEnd: params.planEnd } : {}),
  }

  let response: Response
  try {
    response = await apiFetch(url, {
      method,
      headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })
  } catch {
    throw new TguApiError('Не удалось подключиться к сервису планирования миссий.', 'network')
  }

  const text = await response.text()
  if (!response.ok) {
    throw new TguApiError(
      extractErrorText(text) || 'Ошибка построения маршрута съёмки.',
      'http',
      response.status,
    )
  }
  if (!text.trim()) {
    throw new TguApiError(
      'Сервис планирования миссий вернул пустой ответ.',
      'http',
      response.status,
    )
  }

  return JSON.parse(text) as SurveyRoute
}

function extractErrorText(body: string): string {
  const trimmed = body.trim()
  if (!trimmed) return ''
  try {
    const parsed = JSON.parse(trimmed) as Record<string, unknown>
    for (const field of ['message', 'error', 'reason', 'detail']) {
      const value = parsed[field]
      if (typeof value === 'string' && value.trim()) {
        return value
      }
    }
  } catch {
    return trimmed
  }
  return trimmed
}
