import type {
  TTSRequestHeaders,
  TTSRetryOptions,
  TTSRequestError,
  AudiobookStatusResponse,
  CreateChapterPayload,
  VoicesResponse,
  TTSSegmentsEnsureRequest,
  TTSSegmentsEnsureResponse,
} from '@/types/client';
import type { TTSAudiobookChapter } from '@/types/tts';

/**
 * Executes a function with exponential backoff retry logic
 * @param operation Function to retry
 * @param options Retry configuration options
 * @returns Promise resolving to the operation result
 */
export const withRetry = async <T>(
  operation: () => Promise<T>,
  options: TTSRetryOptions = {}
): Promise<T> => {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    maxDelay = 10000,
    backoffFactor = 2
  } = options;

  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      // Do not retry on explicit cancellation/abort errors - surface them
      // immediately so callers can stop work quickly when the user cancels.
      if (
        lastError.name === 'AbortError'
        || lastError.message.toLowerCase().includes('cancelled')
        || lastError.message.toLowerCase().includes('aborted')
      ) {
        break;
      }

      // Do not retry on payment required / rate limit exceeded (user quota)
      const status = (() => {
        if (typeof error === 'object' && error !== null && 'status' in error) {
          const maybe = (error as { status?: unknown }).status;
          return typeof maybe === 'number' ? maybe : undefined;
        }
        return undefined;
      })();
      const code = (() => {
        if (typeof error === 'object' && error !== null && 'code' in error) {
          const maybe = (error as { code?: unknown }).code;
          return typeof maybe === 'string' ? maybe : undefined;
        }
        return undefined;
      })();
      // Do not retry on user quota exceeded (server tells us when to retry via resetTimeMs/Retry-After)
      if (status === 429 && code === 'USER_DAILY_QUOTA_EXCEEDED') {
        break;
      }

      // Narrow client retries to transport-level failures only.
      // If we got an HTTP status from a server route, do not retry from the client.
      // Server-side routes already apply upstream retry logic where needed.
      const message = lastError.message.toLowerCase();
      const isTransportFailure =
        status === undefined &&
        (
          lastError instanceof TypeError ||
          message.includes('failed to fetch') ||
          message.includes('networkerror') ||
          message.includes('network request failed') ||
          message.includes('load failed') ||
          message.includes('fetch failed') ||
          message.includes('econnreset') ||
          message.includes('etimedout')
        );
      if (!isTransportFailure) {
        break;
      }

      if (attempt === maxRetries - 1) {
        break;
      }

      const delay = Math.min(
        initialDelay * Math.pow(backoffFactor, attempt),
        maxDelay
      );

      console.log(`Retry attempt ${attempt + 1}/${maxRetries} failed. Retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError || new Error('Operation failed after retries');
};

// --- Audiobook API ---



export const getAudiobookStatus = async (bookId: string): Promise<AudiobookStatusResponse> => {
  const response = await fetch(`/api/audiobook/status?bookId=${bookId}`);
  if (!response.ok) {
    throw new Error('Failed to fetch audiobook status');
  }
  return await response.json();
};



export const createAudiobookChapter = async (
  payload: CreateChapterPayload,
  headers: TTSRequestHeaders,
  signal?: AbortSignal
): Promise<TTSAudiobookChapter> => {
  const response = await fetch(`/api/audiobook/chapter`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...headers,
    },
    body: JSON.stringify(payload),
    signal
  });

  if (response.status === 499) {
    throw new Error('cancelled');
  }

  if (response.status === 409) {
    const data = await response.json().catch(() => null) as { error?: string } | null;
    throw new Error(data?.error || 'Audiobook settings mismatch');
  }

  if (!response.ok) {
    let problem: unknown = undefined;
    const contentType = response.headers.get('content-type') || '';
    if (contentType.includes('application/problem+json') || contentType.includes('application/json')) {
      problem = await response.json().catch(() => null);
    }

    const err = new Error(`Audiobook chapter generation failed with status ${response.status}`) as TTSRequestError;
    err.status = response.status;
    if (typeof problem === 'object' && problem !== null) {
      const rec = problem as Record<string, unknown>;
      if (typeof rec.code === 'string') err.code = rec.code;
      if (typeof rec.type === 'string') err.type = rec.type;
      if (typeof rec.title === 'string') err.title = rec.title;
      if (typeof rec.detail === 'string') err.detail = rec.detail;
    } else {
      err.detail = 'Failed to convert audio chapter';
    }

    throw err;
  }

  return await response.json();
};

export const deleteAudiobook = async (bookId: string): Promise<void> => {
  const response = await fetch(`/api/audiobook?bookId=${bookId}`, { method: 'DELETE' });
  if (!response.ok) {
    throw new Error('Reset failed');
  }
};

export const downloadAudiobook = async (bookId: string, format: string): Promise<Response> => {
  const response = await fetch(`/api/audiobook?bookId=${bookId}&format=${format}`);
  if (!response.ok) throw new Error('Download failed');
  return response;
};

export const deleteAudiobookChapter = async (bookId: string, chapterIndex: number): Promise<void> => {
  const response = await fetch(`/api/audiobook/chapter?bookId=${bookId}&chapterIndex=${chapterIndex}`, {
    method: 'DELETE'
  });
  if (!response.ok) {
    throw new Error('Delete failed');
  }
};

export const downloadAudiobookChapter = async (bookId: string, chapterIndex: number): Promise<Blob> => {
  const response = await fetch(`/api/audiobook/chapter?bookId=${bookId}&chapterIndex=${chapterIndex}`);
  if (!response.ok) throw new Error('Download failed');
  return await response.blob();
};

// --- TTS API ---



export const getVoices = async (headers: HeadersInit): Promise<VoicesResponse> => {
  const response = await fetch('/api/tts/voices', {
    headers,
  });

  if (!response.ok) throw new Error('Failed to fetch voices');
  return await response.json();
};

export const ensureTtsSegments = async (
  payload: TTSSegmentsEnsureRequest,
  headers: TTSRequestHeaders,
  signal?: AbortSignal,
): Promise<TTSSegmentsEnsureResponse> => {
  const response = await fetch('/api/tts/segments/ensure', {
    method: 'POST',
    headers: headers as HeadersInit,
    body: JSON.stringify(payload),
    signal,
  });

  if (!response.ok) {
    let problem: unknown = undefined;
    const contentType = response.headers.get('content-type') || '';
    if (contentType.includes('application/problem+json') || contentType.includes('application/json')) {
      problem = await response.json().catch(() => null);
    }

    const err = new Error(`TTS segment ensure failed with status ${response.status}`) as TTSRequestError;
    err.status = response.status;
    if (typeof problem === 'object' && problem !== null) {
      const rec = problem as Record<string, unknown>;
      if (typeof rec.code === 'string') err.code = rec.code;
      if (typeof rec.type === 'string') err.type = rec.type;
      if (typeof rec.title === 'string') err.title = rec.title;
      if (typeof rec.detail === 'string') err.detail = rec.detail;
    }

    throw err;
  }

  return await response.json();
};
