import { AxiosInstance } from "axios";
import { ICustomParams, IDataSource } from "../datasource.interface";
import { defaultAxiosRequestConfig } from "./create-axios-instance";

/**
 * Crea un DataSource basado en Axios.
 * @param client Instancia de Axios que se utilizará para las peticiones HTTP.
 * @returns Un objeto que implementa la interfaz IDataSource.
 * @throws Error si la instancia de Axios no es válida o no tiene la configuración necesaria.
 * @example
 * const axiosInstance = createAxiosInstance({
 *   baseURL: "https://api.example.com",
 *   getAccessToken: () => localStorage.getItem("accessToken"),
 *   onAuthError: () => {
 *     console.error("Error de autenticación");
 *     // Manejar el error de autenticación, por ejemplo, redirigir al login
 *   },
 * });
 * const dataSource = createAxiosDataSource(axiosInstance);
 *
 */

export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
  // Validaciones de la instancia de Axios
  if (!client) {
    throw new Error("[Axios] Se esperaba una instancia de Axios.");
  }

  if (!(client as AxiosInstance).getUri) {
    throw new Error("[Axios] La instancia proporcionada no es una instancia de Axios válida.");
  }

  if (typeof (client as AxiosInstance).getUri !== "function") {
    throw new Error("[Axios] La instancia proporcionada no tiene el método getUri.");
  }

  if (typeof (client as AxiosInstance).getUri() !== "string") {
    throw new Error("[Axios] baseURL no está definido en esta instancia.");
  }

  return {
    getBaseUrl: () => (client as AxiosInstance).getUri(),

    getList: async <T>(resource: string, params?: Record<string, unknown>): Promise<T> => {
      const { signal, ...rest } = params as any; // en 'rest' puede venir el "criteria".

      const res = await (client as AxiosInstance).get<T[]>(resource, { signal, params: rest });
      return <T>res.data;
    },

    getOne: async <T>(resource: string, id: string | number, params?: Record<string, unknown>) => {
      const res = await (client as AxiosInstance).get<T>(`${resource}/${id}`, params);
      return res.data;
    },

    getMany: async <T, R>(resource: string, ids: Array<string | number>): Promise<R> => {
      const res = await (client as AxiosInstance).get<T[]>(`${resource}`, { params: { ids } });
      return <R>res.data;
    },

    createOne: async <T>(
      resource: string,
      data: Partial<T>,
      params?: Record<string, unknown>
    ): Promise<T> => {
      const res = await (client as AxiosInstance).post<T>(resource, data, params);
      return res.data;
    },

    updateOne: async <T>(
      resource: string,
      id: string | number,
      data: Partial<T>,
      params?: Record<string, unknown>
    ) => {
      const res = await (client as AxiosInstance).put<T>(`${resource}/${id}`, data, params);
      return res.data;
    },

    deleteOne: async <T>(
      resource: string,
      id: string | number,
      params?: Record<string, unknown>
    ) => {
      await (client as AxiosInstance).delete(`${resource}/${id}`, params);
    },

    custom: async <T>(customParams: ICustomParams) => {
      const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;

      const requestUrl = path ? `${(client as AxiosInstance).getUri()}/${path}` : url;
      if (!requestUrl) throw new Error('"url" or "path" param is missing');

      const config = {
        url: requestUrl,
        method,
        responseType,
        signal,
        ...payload,
        ...defaultAxiosRequestConfig,
        headers,
      };

      let customResponse;

      switch (method) {
        case "put":
        case "post":
        case "patch":
          customResponse = await (client as AxiosInstance).request<T>({
            ...config,
            data,
          });
          break;

        case "delete":
          customResponse = await (client as AxiosInstance).delete<T>(requestUrl, {
            responseType,
            headers,
            ...payload,
          });
          break;

        default:
          customResponse = await (client as AxiosInstance).get<T>(requestUrl, {
            responseType,
            signal,
            headers,
            ...payload,
          });
          break;
      }

      return customResponse.data;
    },
  };
};
