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 {
  TaskDefinitionDetailed,
  TaskDefinitionDocument,
  TaskDefinitionSummary,
} from '~/types/task_definitions';
import type { Pagination } from '~/types/responses';

/**
 * Reactive list of all task definition summaries, sorted by priority and name.
 */
const definitions = ref<Array<TaskDefinitionSummary>>([]);
/**
 * Pagination state for task definitions 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);
/**
 * 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 task definition summaries by priority (ascending), then name (A-Z).
 * @param items Array of TaskDefinitionSummary
 * @returns Sorted array of TaskDefinitionSummary
 */
const sortSummaries = (items: Array<TaskDefinitionSummary>): Array<TaskDefinitionSummary> => {
  return [...items].sort((a, b) => {
    if (a.priority === b.priority) {
      return a.name.localeCompare(b.name);
    }

    return a.priority - b.priority;
  });
};

/**
 * 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 response
    .clone()
    .json()
    .catch(() => null);
  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 summary in the definitions list, keeping sort order.
 * @param summary TaskDefinitionSummary to update/add
 */
const updateSummaries = (summary: TaskDefinitionSummary): void => {
  const isNew = !definitions.value.some((item) => item.id === summary.id);
  definitions.value = sortSummaries([
    ...definitions.value.filter((item) => item.id !== summary.id),
    summary,
  ]);
  if (isNew) {
    pagination.value.total++;
  }
};

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

/**
 * Loads all task definition summaries from the API.
 * Updates definitions and lastError.
 */
const loadDefinitions = async (
  page: number = 1,
  perPage: number | undefined = undefined,
): Promise<void> => {
  isLoading.value = true;
  try {
    let url = `/api/tasks/definitions/?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<TaskDefinitionSummary>(json);

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

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

    const payload = await response.json();
    const detailed = await parse_api_response<TaskDefinitionDetailed>(payload);
    lastError.value = null;
    return detailed;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Creates a new task definition via API.
 * @param definition TaskDefinitionDocument to create
 * @returns Created TaskDefinitionDetailed or null on error
 */
const createDefinition = async (
  definition: TaskDefinitionDocument,
): Promise<TaskDefinitionDetailed | null> => {
  try {
    const response = await request('/api/tasks/definitions/', {
      method: 'POST',
      body: JSON.stringify(definition),
    });

    await ensureSuccess(response);

    const payload = await parse_api_response<TaskDefinitionDetailed>(response.json());

    updateSummaries({
      id: payload.id,
      name: payload.name,
      priority: payload.priority,
      match_url: payload.match_url,
      enabled: payload.enabled,
      created_at: payload.created_at,
      updated_at: payload.updated_at,
    });

    useNotification().success('Task definition created.');
    lastError.value = null;
    return payload;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

/**
 * Updates an existing task definition via API.
 * @param id Task definition ID
 * @param definition Updated TaskDefinitionDocument
 * @returns Updated TaskDefinitionDetailed or null on error
 */
const updateDefinition = async (
  id: number,
  definition: TaskDefinitionDocument,
): Promise<TaskDefinitionDetailed | null> => {
  try {
    const response = await request(`/api/tasks/definitions/${id}`, {
      method: 'PUT',
      body: JSON.stringify(definition),
    });

    await ensureSuccess(response);

    const payload = await parse_api_response<TaskDefinitionDetailed>(response.json());

    updateSummaries({
      id: payload.id,
      name: payload.name,
      priority: payload.priority,
      match_url: payload.match_url,
      enabled: payload.enabled,
      created_at: payload.created_at,
      updated_at: payload.updated_at,
    });

    useNotification().success('Task definition updated.');
    lastError.value = null;
    return payload;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

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

    removeSummary(id);
    useNotification().success('Task definition deleted.');
    lastError.value = null;
    return true;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return false;
  }
};

/**
 * Toggles the enabled status of a task definition.
 * @param id Task definition ID
 * @param enabled New enabled status
 * @returns Updated TaskDefinitionDetailed or null on error
 */
const toggleEnabled = async (
  id: number,
  enabled: boolean,
): Promise<TaskDefinitionDetailed | null> => {
  try {
    const response = await request(`/api/tasks/definitions/${id}`, {
      method: 'PATCH',
      body: JSON.stringify({ enabled }),
    });

    await ensureSuccess(response);

    const payload = await parse_api_response<TaskDefinitionDetailed>(response.json());

    updateSummaries({
      id: payload.id,
      name: payload.name,
      priority: payload.priority,
      match_url: payload.match_url,
      enabled: payload.enabled,
      created_at: payload.created_at,
      updated_at: payload.updated_at,
    });

    useNotification().success(`Task definition ${enabled ? 'enabled' : 'disabled'}.`);
    lastError.value = null;
    return payload;
  } catch (error) {
    handleError(error);
    if (throwInstead.value) throw error;
    return null;
  }
};

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

/**
 * useTaskDefinitions composable
 *
 * Returns reactive state and CRUD methods for task definitions.
 * @returns Object with state and API methods
 */
export const useTaskDefinitions = () => ({
  definitions: readonly(definitions),
  pagination: readonly(pagination),
  isLoading: readonly(isLoading),
  lastError: readonly(lastError),
  loadDefinitions,
  getDefinition,
  createDefinition,
  updateDefinition,
  deleteDefinition,
  toggleEnabled,
  clearError,
  throwInstead,
});

export default useTaskDefinitions;
