import { apiRequest, getAccessToken, HttpError } from '@/shared/api/client'
import type {
  CreatedIdResponse,
  ImportManualInboxResultDto,
  ImportMoviesResultDto,
  MovieImportItem,
  MovieMatchDto,
  ManualImportItem,
  ManualInboxListDto,
  MediaAssetDto,
  MediaAssetStatus,
  MediaStatsDto,
  PagedList,
} from '@/shared/api/types'

type ListMediaParams = {
  page: number
  pageSize: number
  statuses?: MediaAssetStatus[]
  search?: string
  sort?: string
  desc?: boolean
}

export function listMedia(params: ListMediaParams) {
  const query = new URLSearchParams({
    page: String(params.page),
    pageSize: String(params.pageSize),
  })
  for (const status of params.statuses ?? []) query.append('status', status)
  if (params.search) query.set('search', params.search)
  if (params.sort) query.set('sort', params.sort)
  if (params.desc) query.set('desc', 'true')
  return apiRequest<PagedList<MediaAssetDto>>(`/admin/media?${query.toString()}`)
}

export function getMediaStats() {
  return apiRequest<MediaStatsDto>('/admin/media/stats')
}

/**
 * Дозагружает ВСЕ страницы медиа (для пикеров с клиентской фильтрацией/сортировкой — кандидаты серий,
 * пул рекламы), чтобы ничего не терялось молча за фиксированным pageSize. Есть предохранитель `cap`:
 * если элементов больше — возвращаем `truncated: true`, и UI показывает предупреждение (а не делает вид,
 * что список полон).
 */
export async function listAllMedia(
  params: Omit<ListMediaParams, 'page' | 'pageSize'> & { cap?: number },
): Promise<{ items: MediaAssetDto[]; total: number; truncated: boolean }> {
  const pageSize = 200
  const cap = params.cap ?? 5000
  const items: MediaAssetDto[] = []
  let total = 0
  for (let page = 1; ; page++) {
    const res = await listMedia({
      page,
      pageSize,
      statuses: params.statuses,
      search: params.search,
    })
    total = res.total
    items.push(...res.items)
    if (res.items.length === 0 || items.length >= total || items.length >= cap) break
  }
  return { items, total, truncated: items.length < total }
}

/** Что лежит в ручном inbox (manual/) и ждёт разбора. */
export function listManualInbox() {
  return apiRequest<ManualInboxListDto>('/admin/media/manual')
}

/**
 * Забирает файлы из manual/ в шоу: файлы уходят из каталога, как и из обычного inbox, а спутники
 * (субтитры, nfo) удаляются. Номера серий передаются явно — сохранится ровно то, что было показано.
 */
export function importManualInbox(items: ManualImportItem[], showId: string) {
  return apiRequest<ImportManualInboxResultDto>('/admin/media/manual/import', {
    method: 'POST',
    body: { items, showId },
  })
}

export function deleteMedia(id: string) {
  return apiRequest<void>(`/admin/media/${id}`, { method: 'DELETE' })
}

/**
 * Потоковая загрузка файла (сырое тело + fileName в query). Через XHR ради индикатора прогресса.
 */
export function uploadMedia(
  file: File,
  onProgress?: (percent: number) => void,
  signal?: AbortSignal,
): Promise<CreatedIdResponse> {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) {
      reject(new DOMException('Aborted', 'AbortError'))
      return
    }

    const xhr = new XMLHttpRequest()
    const query = new URLSearchParams({ fileName: file.name })
    xhr.open('POST', `/api/admin/media?${query.toString()}`)

    const token = getAccessToken()
    if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`)

    signal?.addEventListener('abort', () => xhr.abort())
    xhr.onabort = () => reject(new DOMException('Aborted', 'AbortError'))

    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable && onProgress)
        onProgress(Math.round((event.loaded / event.total) * 100))
    }
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText) as CreatedIdResponse)
      } else {
        let detail = `HTTP ${xhr.status}`
        try {
          const problem = JSON.parse(xhr.responseText) as { detail?: string; title?: string }
          detail = problem.detail ?? problem.title ?? detail
        } catch {
          /* пусто */
        }
        reject(new HttpError({ detail }, xhr.status))
      }
    }
    xhr.onerror = () => reject(new HttpError({ title: 'Network error' }, 0))
    xhr.send(file)
  })
}

// ── Разбор фильмов ────────────────────────────────────────────────────────

/**
 * Разбирает имена и ищет кандидатов в источнике. Имена шлём с клиента, потому что источника два:
 * каталог `manual/` и файлы, выбранные в браузере, — а разбирать их надо одинаково и **до** того,
 * как что-то поедет на сервер.
 */
export function matchMovies(names: string[], provider: string) {
  return apiRequest<MovieMatchDto[]>('/admin/media/movies/match', {
    method: 'POST',
    body: { names, provider },
  })
}

/** Перезапрос одной строки: админ поправил название (транслит, другое написание). */
export function matchMovie(name: string, title: string, year: number | null, provider: string) {
  return apiRequest<MovieMatchDto>('/admin/media/movies/match-one', {
    method: 'POST',
    body: { name, title, year, provider },
  })
}

/** Заводит фильмы: шоу на файл, метаданные по выбранному кандидату, файл — в очередь обработки. */
export function importMovies(items: MovieImportItem[], provider: string) {
  return apiRequest<ImportMoviesResultDto>('/admin/media/movies/import', {
    method: 'POST',
    body: { items, provider },
  })
}

/**
 * Плейлист обработанного ассета под admin-роутом (JWT) — грузится через hls.js, а не ставится
 * в <video> напрямую: сегменты лежат за той же авторизацией, что и плейлист.
 */
export function mediaPreviewUrl(assetId: string) {
  return `/api/admin/media/${assetId}/preview/index.m3u8`
}
