import { notifyUnauthorized } from './auth';
import { ApiError, request } from './client';
import type { components } from './generated/v1';

export type TroubleshootingInfo = components['schemas']['TroubleshootingInfoResponseModel'];
export type ValidateScheduleResult = components['schemas']['ValidateSequentialScheduleResponseModel'];
export type TroubleshootingSubtitle = components['schemas']['TroubleshootingSubtitleResponseModel'];
export type TroubleshootingPlaybackStatus = components['schemas']['TroubleshootingPlaybackStatusResponseModel'];
export type StartTroubleshootingPlaybackBody = components['schemas']['StartTroubleshootingPlaybackRequest'];
export type TroubleshootingPlaybackStarted = components['schemas']['TroubleshootingPlaybackStartedResponseModel'];

export function getTroubleshootingInfo(): Promise<TroubleshootingInfo> {
  return request<TroubleshootingInfo>('/api/v1/troubleshoot/info');
}

// The stream selectors available for a troubleshooting playback (channel "smart" selectors).
export function getTroubleshootingStreamSelectors(): Promise<string[]> {
  return request<string[]>('/api/v1/troubleshoot/playback/stream-selectors');
}

// Subtitle streams that can be burned in for a media item. Each item's `id` is the value to pass
// back as the playback.m3u8 endpoint's `subtitleId` query param.
export function getTroubleshootingSubtitles(mediaItemId: number): Promise<TroubleshootingSubtitle[]> {
  return request<TroubleshootingSubtitle[]>(`/api/v1/troubleshoot/playback/subtitles/${mediaItemId}`);
}

// Status of the current or last troubleshooting playback session. `state` is idle|running|
// completed|failed; exitCode/speed are null until completed/failed; logs is the log tail (null
// until written).
export function getTroubleshootingPlaybackStatus(): Promise<TroubleshootingPlaybackStatus> {
  return request<TroubleshootingPlaybackStatus>('/api/v1/troubleshoot/playback/status');
}

// Start a troubleshooting playback session (#295 — was a GET on `playback.m3u8` that started the
// session as a side effect of the manifest fetch; now an explicit POST with a JSON body). The server
// blocks until the first segments exist, then returns the open `/iptv` live manifest URL to feed to
// HlsPlayer. A 404/409/422 surfaces as an ApiError the caller can show inline.
export function startTroubleshootingPlayback(
  body: StartTroubleshootingPlaybackBody
): Promise<TroubleshootingPlaybackStarted> {
  return request<TroubleshootingPlaybackStarted>('/api/v1/troubleshoot/playback/start', {
    body,
    method: 'POST'
  });
}

// File-download endpoints — these return a binary zip, not JSON, so they must NOT go through
// `request()` (which JSON-parses the body). Fetch directly with the CSRF header (both are POST since
// #295), read the blob, and trigger a browser download. Same-origin fetch can read Content-Disposition
// so we use the server-provided filename when present.
export function downloadTroubleshootingArchive(): Promise<void> {
  return downloadViaPost('/api/v1/troubleshoot/playback/archive', 'ersatztv-troubleshooting.zip');
}

export function downloadTroubleshootingMediaSample(id: number): Promise<void> {
  return downloadViaPost(`/api/v1/troubleshoot/playback/sample/${id}`, 'ersatztv-media-sample.zip');
}

async function downloadViaPost(path: string, fallbackName: string): Promise<void> {
  const response = await fetch(path, { method: 'POST', headers: { 'X-Csrf': '1' } });

  if (!response.ok) {
    // This raw fetch bypasses the central client, so emit the app-wide 401 signal ourselves — an
    // expired session mid-download should raise the global re-login banner, not just an inline error.
    if (response.status === 401) {
      notifyUnauthorized();
    }
    throw new ApiError(response.status, await readProblemDetailsSafe(response));
  }

  const blob = await response.blob();
  const filename = filenameFromContentDisposition(response.headers.get('Content-Disposition'), fallbackName);
  const objectUrl = URL.createObjectURL(blob);
  try {
    const anchor = document.createElement('a');
    anchor.href = objectUrl;
    anchor.download = filename;
    document.body.appendChild(anchor);
    anchor.click();
    anchor.remove();
  } finally {
    URL.revokeObjectURL(objectUrl);
  }
}

async function readProblemDetailsSafe(response: Response): Promise<components['schemas']['ProblemDetails'] | undefined> {
  const contentType = response.headers.get('Content-Type') ?? '';
  if (!contentType.includes('application/json') && !contentType.includes('problem+json')) {
    return undefined;
  }
  try {
    return (await response.json()) as components['schemas']['ProblemDetails'];
  } catch {
    return undefined;
  }
}

// Parse `filename="…"` / `filename*=UTF-8''…` from a Content-Disposition header, falling back when the
// header is absent or unparseable.
function filenameFromContentDisposition(header: null | string, fallback: string): string {
  if (!header) {
    return fallback;
  }

  const extended = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
  if (extended?.[1]) {
    try {
      return decodeURIComponent(extended[1].trim().replace(/^"|"$/g, ''));
    } catch {
      // fall through to the plain filename form
    }
  }

  const plain = /filename="?([^";]+)"?/i.exec(header);
  if (plain?.[1]) {
    return plain[1].trim();
  }

  return fallback;
}

export function validateSequentialSchedule(yaml: string, isImport: boolean): Promise<ValidateScheduleResult> {
  return request<ValidateScheduleResult>('/api/v1/troubleshoot/validate-schedule', {
    body: { yaml, isImport },
    method: 'POST'
  });
}

export function messageFromTroubleshootError(error: unknown, fallback = 'Unable to load troubleshooting info'): string {
  if (error instanceof ApiError) {
    return error.detail ?? error.message;
  }

  if (error instanceof Error) {
    return error.message;
  }

  return fallback;
}
