import { apiDownload, apiRequest } from '@/shared/api/client'
import type {
  ApplyResultDto,
  ChannelDto,
  ChannelSummaryDto,
  CopyTemplateResultDto,
  CreatedIdResponse,
  EntryTraceDto,
  GenerateGridResultDto,
  GridConfig,
  GridGenerationMode,
  GridImportResultDto,
  GridPromptDto,
  GridPlanDto,
  GridProfileDto,
  GridProfileKind,
  LayerApplicability,
  PlanningRules,
  ResetTemplateResultDto,
  RestoreTemplateResultDto,
  ScheduleDiffDto,
  ScheduleEntryDto,
  SchedulePreviewDto,
  ScheduleTemplateDto,
  SlotDto,
  TemplateIssueDto,
  ViewerSettings,
} from '@/shared/api/types'

export function listChannels() {
  return apiRequest<ChannelSummaryDto[]>('/admin/channels')
}

export function getChannel(id: string) {
  return apiRequest<ChannelDto>(`/admin/channels/${id}`)
}

export function createChannel(body: { name: string; slug: string }) {
  return apiRequest<CreatedIdResponse>('/admin/channels', { method: 'POST', body })
}

type ChannelSettingsBody = {
  name: string
  isEnabled: boolean
  fillerAssetId: string | null
}

export function updateChannelSettings(id: string, body: ChannelSettingsBody) {
  return apiRequest<void>(`/admin/channels/${id}/settings`, { method: 'PUT', body })
}

/** Оверлеи и аналоговый фильтр канала — как он выглядит у зрителя. */
export function updateViewerSettings(id: string, body: ViewerSettings) {
  return apiRequest<void>(`/admin/channels/${id}/viewer`, { method: 'PUT', body })
}

/** Номер канала и его время: смещение от UTC и начало вещательных суток. */
export function updateChannelTime(
  id: string,
  body: { number: number | null; utcOffsetMinutes: number; dayStartTime: string },
) {
  return apiRequest<void>(`/admin/channels/${id}/time`, { method: 'PUT', body })
}

// ── Сетка канала ──────────────────────────────────────────────────────────

export function getChannelTemplate(channelId: string) {
  return apiRequest<ScheduleTemplateDto>(`/admin/channels/${channelId}/template`)
}

/** Заводит каналу сетку, если её нет: у каналов из старой ротации шаблона может не быть. */
export function createChannelTemplate(channelId: string) {
  return apiRequest<CreatedIdResponse>(`/admin/channels/${channelId}/template`, { method: 'POST' })
}

/** Применяет правила к эфиру: пересобирает будущий хвост. Правка слотов эфир не двигает. */
export function applyChannelTemplate(channelId: string) {
  return apiRequest<ApplyResultDto>(`/admin/channels/${channelId}/template/apply`, {
    method: 'POST',
  })
}

/**
 * Откат правок сетки к состоянию, по которому идёт эфир: восстанавливает слои, слоты и правила
 * из снимка последнего применения. Ленту не трогает — она и так по этому состоянию.
 */
export function restoreChannelTemplate(channelId: string) {
  return apiRequest<RestoreTemplateResultDto>(`/admin/channels/${channelId}/template/restore`, {
    method: 'POST',
  })
}

/**
 * Полный сброс: снимает сетку и удаляет будущий эфир, собранный по ней. Прошлое и идущая сейчас
 * запись остаются — их нельзя вырезать из-под зрителя.
 */
export function resetChannelTemplate(channelId: string) {
  return apiRequest<ResetTemplateResultDto>(`/admin/channels/${channelId}/template/reset`, {
    method: 'POST',
  })
}

/** Проверки сетки по правилам — считаются по шаблону, без прогона генератора. */
export function getTemplateIssues(channelId: string) {
  return apiRequest<TemplateIssueDto[]>(`/admin/channels/${channelId}/template/issues`)
}

/** Что изменится в эфире, если применить сейчас. Прогон сухой — лента не трогается. */
export function getApplyDiff(channelId: string) {
  return apiRequest<ScheduleDiffDto>(`/admin/channels/${channelId}/template/diff`)
}

/** Каталог профилей автосборки: имя, на что похоже и для какой библиотеки годится. */
export function listGridProfiles() {
  return apiRequest<GridProfileDto[]>('/admin/grid-profiles')
}

/** План автосборки: что будет создано и что снесено. Считается тем же кодом, что и сама сборка. */
export function previewGrid(channelId: string, profile: GridProfileKind, mode: GridGenerationMode) {
  const query = new URLSearchParams({ profile, mode })
  return apiRequest<GridPlanDto>(
    `/admin/channels/${channelId}/template/grid-plan?${query.toString()}`,
  )
}

/** Собирает сетку по профилю. План пересчитывается на сервере — с клиента едут только опции. */
export function generateGrid(
  channelId: string,
  profile: GridProfileKind,
  mode: GridGenerationMode,
) {
  return apiRequest<GenerateGridResultDto>(`/admin/channels/${channelId}/template/generate`, {
    method: 'POST',
    body: { profile, mode },
  })
}

/** Выгрузка сетки одним файлом: перенос, бэкап перед экспериментом и образец для ИИ. */
export function exportGrid(channelId: string) {
  return apiRequest<GridConfig>(`/admin/channels/${channelId}/template/export`)
}

/**
 * Загрузка сетки из файла. `replace` — снести существующие слоты и построить заново; иначе слои
 * и слоты добавляются к тому, что уже есть.
 */
export function importGrid(channelId: string, config: GridConfig, replace: boolean) {
  return apiRequest<GridImportResultDto>(`/admin/channels/${channelId}/template/import`, {
    method: 'POST',
    body: { config, replace },
  })
}

/**
 * Собирает текст запроса к ИИ по референс-каналам и пожеланиям. Никуда не отправляется — админ
 * копирует его в свою модель и приносит ответ назад кнопкой импорта.
 */
export function buildGridPrompt(channelId: string, references: string[], notes: string) {
  return apiRequest<GridPromptDto>(`/admin/channels/${channelId}/template/ai-prompt`, {
    method: 'POST',
    body: { references, notes: notes || null },
  })
}

/** Копия сетки на другой канал: слои, слоты, стыки и правила. Группы общие и не копируются. */
export function copyTemplateTo(channelId: string, targetChannelId: string) {
  return apiRequest<CopyTemplateResultDto>(
    `/admin/channels/${channelId}/template/copy-to/${targetChannelId}`,
    { method: 'POST' },
  )
}

/** Цепочка происхождения записи, записанная в момент генерации. */
export function getEntryTrace(entryId: string) {
  return apiRequest<EntryTraceDto>(`/admin/channels/entries/${entryId}/trace`)
}

/** Сухой прогон по текущим правилам: ничего не пишет и не двигает курсоры слотов. */
export function previewTemplate(channelId: string, days: number) {
  const query = new URLSearchParams({ days: String(days) })
  return apiRequest<SchedulePreviewDto>(
    `/admin/channels/${channelId}/template/preview?${query.toString()}`,
  )
}

export function updateTemplate(
  templateId: string,
  body: {
    name: string
    fallbackGroupId: string | null
    defaultJunctionId: string | null
    rules: PlanningRules | null
  },
) {
  return apiRequest<void>(`/admin/templates/${templateId}`, { method: 'PUT', body })
}

export function createLayer(templateId: string, body: { name: string; priority: number }) {
  return apiRequest<CreatedIdResponse>(`/admin/templates/${templateId}/layers`, {
    method: 'POST',
    body,
  })
}

export function updateLayer(
  layerId: string,
  body: {
    name: string
    priority: number
    applicability: LayerApplicability | null
    isEnabled: boolean
  },
) {
  return apiRequest<void>(`/admin/layers/${layerId}`, { method: 'PUT', body })
}

export function deleteLayer(layerId: string) {
  return apiRequest<void>(`/admin/layers/${layerId}`, { method: 'DELETE' })
}

/** Тело слота: то же для создания и правки (см. SlotInput на сервере). */
export type SlotBody = Omit<SlotDto, 'id' | 'layerId' | 'groupName'>

/** Слот из ответа сервера → тело запроса: отбрасываем то, что сервер проставляет сам. */
export function toSlotBody(slot: SlotDto): SlotBody {
  const { id: _id, layerId: _layerId, groupName: _groupName, ...body } = slot
  return body
}

export function createSlot(layerId: string, body: SlotBody) {
  return apiRequest<CreatedIdResponse>(`/admin/layers/${layerId}/slots`, { method: 'POST', body })
}

export function updateSlot(slotId: string, body: SlotBody) {
  return apiRequest<void>(`/admin/slots/${slotId}`, { method: 'PUT', body })
}

export function deleteSlot(slotId: string) {
  return apiRequest<void>(`/admin/slots/${slotId}`, { method: 'DELETE' })
}

// ── Стыки канала ──────────────────────────────────────────────────────────

export function getSchedule(id: string, from: Date, to: Date) {
  const query = new URLSearchParams({ from: from.toISOString(), to: to.toISOString() })
  return apiRequest<ScheduleEntryDto[]>(`/admin/channels/${id}/schedule?${query.toString()}`)
}

/**
 * Отладочный дамп канала: вход планировщика, состояние слотов, лента и сухой прогон одним архивом.
 * Тянем через fetch, а не ссылкой: эндпоинт закрыт Bearer'ом, и `<a href>` заголовок не отправит.
 */
export async function exportChannelDebug(channelId: string) {
  const { blob, fileName } = await apiDownload(
    `/admin/channels/${channelId}/debug-export`,
    'telewave-debug.zip',
    { method: 'POST' },
  )
  const url = URL.createObjectURL(blob)
  try {
    const link = document.createElement('a')
    link.href = url
    link.download = fileName
    link.click()
  } finally {
    URL.revokeObjectURL(url)
  }
  return fileName
}
