import { useState, useCallback, useRef } from 'react';
import { apiClient } from '../lib/api';
import { generateFileId, generateTitleFromFilename } from '../lib/fileUtils';
import type { RequestedOutputs } from '../types/api';
import type { UploadStatus, UploadProgressItem } from '../components/UploadProgressList';

const CHUNK_SIZE = 8 * 1024 * 1024; // 8 MB per chunk
const CHUNKED_THRESHOLD = 100 * 1024 * 1024; // use chunked path above 100 MB

function putChunk(url: string, chunk: Blob, start: number, end: number, total: number): Promise<void> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('PUT', url);
    xhr.setRequestHeader('Content-Range', `bytes ${start}-${end}/${total}`);
    // GCS resumable PUT expects the blob's content-type
    xhr.setRequestHeader('Content-Type', chunk.type || 'application/octet-stream');
    xhr.onload = () => {
      // 200/201 = final chunk accepted; 308 = intermediate chunk accepted
      if (xhr.status === 200 || xhr.status === 201 || xhr.status === 308) {
        resolve();
      } else {
        reject(new Error(`Chunk upload failed: HTTP ${xhr.status}`));
      }
    };
    xhr.onerror = () => reject(new Error('Network error during chunk upload'));
    xhr.send(chunk);
  });
}

export interface FileListItem {
  id: string;
  file: File;
  autoTitle: string;
}

export interface SharedJobSettings {
  requestedOutputs: RequestedOutputs;
  brandContext?: string;
  projectId?: string;
  deadline?: string;
  initialLinguistId?: string;
  initialReviewerId?: string;
}

interface UseMultiUploadOptions {
  maxConcurrent?: number;
  onJobCreated?: (jobId: string, filename: string) => void;
  onAllComplete?: (results: UploadProgressItem[]) => void;
}

interface UseMultiUploadReturn {
  // State
  files: FileListItem[];
  uploads: UploadProgressItem[];
  isUploading: boolean;
  isComplete: boolean;

  // Actions
  addFiles: (newFiles: File[]) => void;
  removeFile: (id: string) => void;
  clearFiles: () => void;
  startUpload: (settings: SharedJobSettings) => Promise<UploadProgressItem[]>;
  retryFailed: (settings: SharedJobSettings) => Promise<void>;
  reset: () => void;

  // Computed
  totalFiles: number;
  completedCount: number;
  failedCount: number;
  successCount: number;
}

export function useMultiUpload(options: UseMultiUploadOptions = {}): UseMultiUploadReturn {
  const { maxConcurrent = 3, onJobCreated, onAllComplete } = options;

  const [files, setFiles] = useState<FileListItem[]>([]);
  const [uploads, setUploads] = useState<UploadProgressItem[]>([]);
  const [isUploading, setIsUploading] = useState(false);
  const [isComplete, setIsComplete] = useState(false);

  // Store settings ref for retry
  const settingsRef = useRef<SharedJobSettings | null>(null);

  const addFiles = useCallback((newFiles: File[]) => {
    const items: FileListItem[] = newFiles.map(file => ({
      id: generateFileId(),
      file,
      autoTitle: generateTitleFromFilename(file.name),
    }));
    setFiles(prev => [...prev, ...items]);
    // Reset complete state when adding new files
    setIsComplete(false);
    setUploads([]);
  }, []);

  const removeFile = useCallback((id: string) => {
    setFiles(prev => prev.filter(f => f.id !== id));
  }, []);

  const clearFiles = useCallback(() => {
    setFiles([]);
    setUploads([]);
    setIsComplete(false);
  }, []);

  const reset = useCallback(() => {
    setFiles([]);
    setUploads([]);
    setIsUploading(false);
    setIsComplete(false);
    settingsRef.current = null;
  }, []);

  const updateUploadProgress = useCallback((id: string, update: Partial<UploadProgressItem>) => {
    setUploads(prev => prev.map(u => u.id === id ? { ...u, ...update } : u));
  }, []);

  const uploadChunkedFile = useCallback(async (
    item: FileListItem,
    settings: SharedJobSettings,
  ): Promise<UploadProgressItem> => {
    const uploadItem: UploadProgressItem = {
      id: item.id,
      filename: item.file.name,
      autoTitle: item.autoTitle,
      status: 'uploading',
      progress: 0,
    };
    updateUploadProgress(item.id, { status: 'uploading', progress: 0 });

    try {
      const { job_id, upload_url } = await apiClient.initChunkedUpload(
        item.file.name,
        item.file.type || 'video/mp4',
        item.file.size,
      );

      // Upload in chunks directly to GCS (bypasses LB timeout)
      const totalChunks = Math.ceil(item.file.size / CHUNK_SIZE);
      for (let i = 0; i < totalChunks; i++) {
        const start = i * CHUNK_SIZE;
        const end = Math.min(start + CHUNK_SIZE, item.file.size) - 1;
        const chunk = item.file.slice(start, end + 1);
        await putChunk(upload_url, chunk, start, end, item.file.size);
        // Report 0–95% during chunk upload; last 5% for backend complete call
        const progress = Math.round(((end + 1) / item.file.size) * 95);
        updateUploadProgress(item.id, { progress });
      }

      const job = await apiClient.completeChunkedUpload({
        job_id,
        title: item.autoTitle,
        original_filename: item.file.name,
        requested_outputs: settings.requestedOutputs as unknown as Record<string, unknown>,
        brand_context: settings.brandContext,
        project_id: settings.projectId,
        deadline: settings.deadline,
        initial_linguist_id: settings.initialLinguistId,
        initial_reviewer_id: settings.initialReviewerId,
      });

      const result: UploadProgressItem = { ...uploadItem, status: 'success', progress: 100, jobId: job.id };
      updateUploadProgress(item.id, result);
      onJobCreated?.(job.id, item.file.name);
      return result;
    } catch (error) {
      const errorMessage = extractErrorMessage(error);
      const result: UploadProgressItem = { ...uploadItem, status: 'error', error: errorMessage };
      updateUploadProgress(item.id, result);
      return result;
    }
  }, [updateUploadProgress, onJobCreated]);

  const uploadSingleFile = useCallback(async (
    item: FileListItem,
    settings: SharedJobSettings
  ): Promise<UploadProgressItem> => {
    // Route large files through GCS resumable upload to bypass LB timeout
    if (item.file.size > CHUNKED_THRESHOLD) {
      return uploadChunkedFile(item, settings);
    }

    const uploadItem: UploadProgressItem = {
      id: item.id,
      filename: item.file.name,
      autoTitle: item.autoTitle,
      status: 'uploading',
      progress: 0,
    };

    updateUploadProgress(item.id, { status: 'uploading', progress: 0 });

    try {
      const job = await apiClient.createJob(
        {
          title: item.autoTitle,
          requested_outputs: settings.requestedOutputs,
          brand_context: settings.brandContext,
          project_id: settings.projectId,
          deadline: settings.deadline,
          initial_linguist_id: settings.initialLinguistId,
          initial_reviewer_id: settings.initialReviewerId,
        },
        item.file,
        (progressEvent) => {
          const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
          updateUploadProgress(item.id, { progress: percent });
        }
      );

      const result: UploadProgressItem = {
        ...uploadItem,
        status: 'success',
        progress: 100,
        jobId: job.id,
      };
      updateUploadProgress(item.id, result);
      onJobCreated?.(job.id, item.file.name);
      return result;
    } catch (error) {
      const errorMessage = extractErrorMessage(error);
      const result: UploadProgressItem = {
        ...uploadItem,
        status: 'error',
        error: errorMessage,
      };
      updateUploadProgress(item.id, result);
      return result;
    }
  }, [updateUploadProgress, onJobCreated, uploadChunkedFile]);

  const startUpload = useCallback(async (settings: SharedJobSettings): Promise<UploadProgressItem[]> => {
    if (files.length === 0) return [];

    settingsRef.current = settings;
    setIsUploading(true);
    setIsComplete(false);

    // Initialize upload items
    const initialUploads: UploadProgressItem[] = files.map(f => ({
      id: f.id,
      filename: f.file.name,
      autoTitle: f.autoTitle,
      status: 'pending' as UploadStatus,
      progress: 0,
    }));
    setUploads(initialUploads);

    const results: UploadProgressItem[] = [];
    const queue = [...files];
    const active: Promise<void>[] = [];

    // Process queue with concurrency limit
    while (queue.length > 0 || active.length > 0) {
      // Fill up to maxConcurrent
      while (active.length < maxConcurrent && queue.length > 0) {
        const item = queue.shift()!;
        const promise = uploadSingleFile(item, settings)
          .then(result => {
            results.push(result);
          })
          .finally(() => {
            const index = active.indexOf(promise);
            if (index > -1) active.splice(index, 1);
          });
        active.push(promise);
      }

      // Wait for at least one to complete
      if (active.length > 0) {
        await Promise.race(active);
      }
    }

    setIsUploading(false);
    setIsComplete(true);
    onAllComplete?.(results);
    return results;
  }, [files, maxConcurrent, uploadSingleFile, onAllComplete]);

  const retryFailed = useCallback(async (settings: SharedJobSettings): Promise<void> => {
    const failedItems = uploads
      .filter(u => u.status === 'error')
      .map(u => files.find(f => f.id === u.id))
      .filter((f): f is FileListItem => f !== undefined);

    if (failedItems.length === 0) return;

    settingsRef.current = settings;
    setIsUploading(true);
    setIsComplete(false);

    // Reset failed items to pending
    setUploads(prev => prev.map(u =>
      u.status === 'error' ? { ...u, status: 'pending' as UploadStatus, error: undefined, progress: 0 } : u
    ));

    const queue = [...failedItems];
    const active: Promise<void>[] = [];

    while (queue.length > 0 || active.length > 0) {
      while (active.length < maxConcurrent && queue.length > 0) {
        const item = queue.shift()!;
        const promise: Promise<void> = uploadSingleFile(item, settings)
          .then(() => {})
          .finally(() => {
            const index = active.indexOf(promise);
            if (index > -1) active.splice(index, 1);
          });
        active.push(promise);
      }

      if (active.length > 0) {
        await Promise.race(active);
      }
    }

    setIsUploading(false);
    setIsComplete(true);
  }, [files, uploads, maxConcurrent, uploadSingleFile]);

  // Computed values
  const totalFiles = files.length;
  const completedCount = uploads.filter(u => u.status === 'success' || u.status === 'error').length;
  const successCount = uploads.filter(u => u.status === 'success').length;
  const failedCount = uploads.filter(u => u.status === 'error').length;

  return {
    files,
    uploads,
    isUploading,
    isComplete,
    addFiles,
    removeFile,
    clearFiles,
    startUpload,
    retryFailed,
    reset,
    totalFiles,
    completedCount,
    failedCount,
    successCount,
  };
}

interface AxiosErrorLike {
  code?: string;
  message?: string;
  response?: {
    status?: number;
    data?: { detail?: string };
  };
}

function extractErrorMessage(error: unknown): string {
  const err = error as AxiosErrorLike;

  if (err.code === 'ERR_NETWORK') {
    return 'Network error - cannot reach server';
  }
  if (err.code === 'ECONNABORTED' || err.message?.includes('timeout')) {
    return 'Upload timeout - file may be too large';
  }
  if (err.response?.status === 413) {
    return 'File too large (max 500MB)';
  }
  if (err.response?.status === 400) {
    return err.response?.data?.detail || 'Invalid file or request';
  }
  if (err.response?.status === 401) {
    return 'Session expired - please log in again';
  }
  if (err.response?.status === 403) {
    return 'Permission denied';
  }
  if (err.response?.status === 500) {
    return 'Server error - please try again';
  }

  return err.message || 'Unknown error';
}
