import { ref, readonly } from 'vue';
import { useNotification } from '~/composables/useNotification';
import { request, parse_list_response, parse_api_response, parse_api_error } from '~/utils';
import type {
  Task,
  TaskPatch,
  TaskInspectRequest,
  TaskInspectResponse,
  TaskMetadataResponse,
} from '~/types/tasks';
import type { APIResponse, Pagination } from '~/types/responses';

/**
 * List of all tasks in memory.
 */
const tasks = ref<Array<Task>>([]);
/**
 * Pagination state for tasks list.
 */
const pagination = ref<Pagination>({
  page: 1,
  per_page: 50,
  total: 0,
  total_pages: 0,
  has_next: false,
  has_prev: false,
});
/**
 * Indicates if a request is in progress.
 */
const isLoading = ref<boolean>(false);
/**
 * Indicates if an add/update operation is in progress.
 */
const addInProgress = ref<boolean>(false);
/**
 * Set of task IDs that are currently in progress.
 */
const inProgressIds = ref<Set<number>>(new Set());
/**
 * Stores the last error message, if any.
 */
const lastError = ref<string | null>(null);
/**
 * If true, methods will throw errors instead of returning null/false (for testing)
 */
const throwInstead = ref(false);
/**
 * Notification composable for showing success/error messages.
 */

/**
 * Sorts tasks by name (A-Z).
 * @param items Array of tasks
 * @returns Sorted array of tasks
 */
const sortTasks = (items: Array<Task>): Array<Task> => {
  return [...items].sort((a, b) => a.name.localeCompare(b.name));
};

/**
 * Safely reads JSON from a Response, returns null on error.
 * @param response Fetch Response object
 * @returns Parsed JSON or null
 */
const readJson = async (response: Response): Promise<unknown> => {
  try {
    const clone = response.clone();
    return await clone.json();
  } catch {
    return null;
  }
};

/**
 * Throws an error if the response is not OK, using API error message if available.
 * @param response Fetch Response object
 * @throws Error with message from API or status code
 */
const ensureSuccess = async (response: Response): Promise<void> => {
  if (response.ok) {
    return;
  }

  const payload = await readJson(response);
  const message = await parse_api_error(payload);
  throw new Error(message);
};

/**
 * Handles errors by updating lastError and showing a notification.
 * @param error Error object or unknown
 */
const handleError = (error: unknown): void => {
  const message = error instanceof Error ? error.message : 'Unexpected error occurred.';
  lastError.value = message;
  useNotification().error(message);
};

/**
 * Updates or adds a task in the list, keeping sort order.
 * Also increments pagination.total if it's a new task.
 * @param item Task to update/add
 */
const updateTasksList = (item: Task): void => {
  const isNew = !tasks.value.some((existing) => existing.id === item.id);
  tasks.value = sortTasks([...tasks.value.filter((existing) => existing.id !== item.id), item]);
  if (isNew) {
    pagination.value.total++;
  }
};

/**
 * Removes a task from the list by ID.
 * Also decrements pagination.total.
 * @param id Task ID
 */
const removeTask = (id: number) => {
  const initialLength = tasks.value.length;
  tasks.value = tasks.value.filter((item) => item.id !== id);
  if (tasks.value.length < initialLength) {
    pagination.value.total = Math.max(0, pagination.value.total - 1);
  }
};

/**
 * Loads tasks from the API with pagination support.
 * @param page Page number
 * @param perPage Items per page
 */
const loadTasks = async (
  page: number = 1,
  perPage: number | undefined = undefined,
): Promise<void> => {
  isLoading.value = true;
  try {
    let url = `/api/tasks/?page=${page}`;
    if (perPage !== undefined) {
      url += `&per_page=${perPage}`;
    }
    const response = await request(url);
    await ensureSuccess(response);

    const json = await response.json();
    const { items, pagination: paginationData } = await parse_list_response<Task>(json);

    tasks.value = sortTasks(items);
    pagination.value = paginationData;
    lastError.value = null;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
  } finally {
    isLoading.value = false;
  }
};

/**
 * Fetches a single task by ID from the API.
 * @param id Task ID
 * @returns Task or null on error
 */
const getTask = async (id: number): Promise<Task | null> => {
  try {
    const response = await request(`/api/tasks/${id}`);
    await ensureSuccess(response);

    const json = await response.json();
    const task = await parse_api_response<Task>(json);

    lastError.value = null;
    return task;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Creates a new task via API.
 * @param task Task to create (single task or array for batch)
 * @param callback Optional callback with APIResponse result
 * @returns Created task(s) or null on error
 */
const createTask = async (
  task:
    | Omit<Task, 'id' | 'created_at' | 'updated_at'>
    | Omit<Task, 'id' | 'created_at' | 'updated_at'>[],
  callback?: (response: APIResponse<Task | Task[]>) => void,
): Promise<Task | Task[] | null> => {
  addInProgress.value = true;
  try {
    const response = await request('/api/tasks/', {
      method: 'POST',
      body: JSON.stringify(task),
    });
    await ensureSuccess(response);

    const json = await response.json();
    const created = await parse_api_response<Task | Array<Task>>(json);

    if (Array.isArray(created)) {
      useNotification().success(`${created.length} tasks created.`);
      created.forEach((t) => updateTasksList(t));
      lastError.value = null;

      if (callback) {
        callback({ success: true, error: null, detail: null, data: created });
      }

      return created;
    }

    updateTasksList(created);
    useNotification().success('Task created.');
    lastError.value = null;

    if (callback) {
      callback({ success: true, error: null, detail: null, data: created });
    }

    return created;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unexpected error occurred.';
    handleError(error);

    if (callback) {
      callback({ success: false, error: errorMessage, detail: error, data: undefined });
    }

    if (throwInstead.value) throw error;
    return null;
  } finally {
    addInProgress.value = false;
  }
};

/**
 * Updates an existing task via API (PUT - full update).
 * @param id Task ID
 * @param task Updated task data
 * @param callback Optional callback with APIResponse result
 * @returns Updated task or null on error
 */
const updateTask = async (
  id: number,
  task: Omit<Task, 'id' | 'created_at' | 'updated_at'>,
  callback?: (response: APIResponse<Task>) => void,
): Promise<Task | null> => {
  addInProgress.value = true;
  try {
    // Explicitly remove id, created_at, updated_at fields if present
    const { id: _, created_at: __, updated_at: ___, ...taskData } = task as Task;

    const response = await request(`/api/tasks/${id}`, {
      method: 'PUT',
      body: JSON.stringify(taskData),
    });
    await ensureSuccess(response);

    const json = await response.json();
    const updated = await parse_api_response<Task>(json);

    updateTasksList(updated);
    useNotification().success(`Task '${updated.name}' updated.`);
    lastError.value = null;

    if (callback) {
      callback({ success: true, error: null, detail: null, data: updated });
    }

    return updated;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unexpected error occurred.';
    handleError(error);

    if (callback) {
      callback({ success: false, error: errorMessage, detail: error, data: undefined });
    }

    if (throwInstead.value) throw error;
    return null;
  } finally {
    addInProgress.value = false;
  }
};

/**
 * Partially updates an existing task via API (PATCH).
 * @param id Task ID
 * @param patch Partial task data to update
 * @param callback Optional callback with APIResponse result
 * @returns Updated task or null on error
 */
const patchTask = async (
  id: number,
  patch: TaskPatch,
  callback?: (response: APIResponse<Task>) => void,
): Promise<Task | null> => {
  addInProgress.value = true;
  try {
    const response = await request(`/api/tasks/${id}`, {
      method: 'PATCH',
      body: JSON.stringify(patch),
    });
    await ensureSuccess(response);

    const json = await response.json();
    const updated = await parse_api_response<Task>(json);

    updateTasksList(updated);
    useNotification().success(`Task '${updated.name}' updated.`);
    lastError.value = null;

    if (callback) {
      callback({ success: true, error: null, detail: null, data: updated });
    }

    return updated;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unexpected error occurred.';
    handleError(error);

    if (callback) {
      callback({ success: false, error: errorMessage, detail: error, data: undefined });
    }

    if (throwInstead.value) throw error;
    return null;
  } finally {
    addInProgress.value = false;
  }
};

/**
 * Deletes a task by ID via API.
 * @param id Task ID
 * @param callback Optional callback with APIResponse result
 * @returns true if deleted, false on error
 */
const deleteTask = async (
  id: number,
  callback?: (response: APIResponse<boolean>) => void,
): Promise<boolean> => {
  try {
    const response = await request(`/api/tasks/${id}`, { method: 'DELETE' });
    await ensureSuccess(response);

    removeTask(id);
    useNotification().success('Task deleted.');
    lastError.value = null;

    if (callback) {
      callback({ success: true, error: null, detail: null, data: true });
    }

    return true;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unexpected error occurred.';
    handleError(error);

    if (callback) {
      callback({ success: false, error: errorMessage, detail: error, data: false });
    }

    if (throwInstead.value) throw error;
    return false;
  }
};

/**
 * Inspects a task handler to check if it can process the given URL.
 * @param request Task inspect request parameters
 * @returns Inspect result or null on error
 */
const inspectTaskHandler = async (
  request: TaskInspectRequest,
): Promise<TaskInspectResponse | null> => {
  try {
    const response = await fetch('/api/tasks/inspect', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request),
    });

    const json = await response.json();
    lastError.value = null;
    return json as TaskInspectResponse;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Marks all items from a task as downloaded in the download archive.
 * @param id Task ID
 * @returns Success message or null on error
 */
const markTaskItems = async (id: number): Promise<string | null> => {
  try {
    const response = await request(`/api/tasks/${id}/mark`, { method: 'POST' });
    await ensureSuccess(response);

    const json = await response.json();
    const message = json.message || 'All items marked as downloaded.';

    useNotification().success(message);
    lastError.value = null;
    return message;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Removes all items from a task from the download archive.
 * @param id Task ID
 * @returns Success message or null on error
 */
const unmarkTaskItems = async (id: number): Promise<string | null> => {
  try {
    const response = await request(`/api/tasks/${id}/mark`, { method: 'DELETE' });
    await ensureSuccess(response);

    const json = await response.json();
    const message = json.message || 'All items removed from archive.';

    useNotification().success(message);
    lastError.value = null;
    return message;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Generates metadata for a task (tvshow.nfo, info.json, thumbnails).
 * @param id Task ID
 * @returns Metadata response or null on error
 */
const generateTaskMetadata = async (id: number): Promise<TaskMetadataResponse | null> => {
  try {
    const response = await request(`/api/tasks/${id}/metadata`, { method: 'POST' });
    await ensureSuccess(response);

    const json = await response.json();
    const metadata = await parse_api_response<TaskMetadataResponse>(json);

    useNotification().success('Metadata generation completed.');
    lastError.value = null;
    return metadata;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Clears the last error message.
 */
const clearError = () => (lastError.value = null);

/**
 * Checks if a task is currently in progress.
 * @param id Task ID
 * @returns true if the task is in progress
 */
const isTaskInProgress = (id: number): boolean => inProgressIds.value.has(id);

/**
 * Sets a task as in progress.
 * @param id Task ID
 */
const setTaskInProgress = (id: number): void => {
  inProgressIds.value.add(id);
};

/**
 * Clears the in progress state for a task.
 * @param id Task ID
 */
const clearTaskInProgress = (id: number): void => {
  inProgressIds.value.delete(id);
};

/**
 * Resets all state to initial values (for testing only).
 */
const __resetForTesting = () => {
  tasks.value = [];
  pagination.value = {
    page: 1,
    per_page: 50,
    total: 0,
    total_pages: 0,
    has_next: false,
    has_prev: false,
  };
  isLoading.value = false;
  addInProgress.value = false;
  lastError.value = null;
  throwInstead.value = false;
  inProgressIds.value = new Set();
};

/**
 * useTasks composable
 *
 * Returns reactive state and CRUD methods for tasks.
 * @returns Object with state and API methods
 */
export const useTasks = () => ({
  tasks: readonly(tasks),
  pagination: readonly(pagination),
  isLoading: readonly(isLoading),
  addInProgress: readonly(addInProgress),
  lastError: readonly(lastError),
  inProgressIds: readonly(inProgressIds),
  isTaskInProgress,
  setTaskInProgress,
  clearTaskInProgress,
  loadTasks,
  getTask,
  createTask,
  updateTask,
  patchTask,
  deleteTask,
  inspectTaskHandler,
  markTaskItems,
  unmarkTaskItems,
  generateTaskMetadata,
  clearError,
  throwInstead,
  __resetForTesting,
});
