/**
 * @fileoverview Redux action creators for AirDC++ integration.
 * Provides actions for Direct Connect protocol operations including
 * search, download management, and socket connection handling.
 * @module actions/airdcpp
 */

import {
  SearchQuery,
  SearchInstance,
  PriorityEnum,
  SearchResponse,
} from "threetwo-ui-typings";
import {
  LIBRARY_SERVICE_BASE_URI,
  SEARCH_SERVICE_BASE_URI,
} from "../constants/endpoints";
import {
  AIRDCPP_SEARCH_RESULTS_ADDED,
  AIRDCPP_SEARCH_RESULTS_UPDATED,
  AIRDCPP_HUB_SEARCHES_SENT,
  AIRDCPP_RESULT_DOWNLOAD_INITIATED,
  AIRDCPP_DOWNLOAD_PROGRESS_TICK,
  AIRDCPP_BUNDLES_FETCHED,
  AIRDCPP_SEARCH_IN_PROGRESS,
  AIRDCPP_FILE_DOWNLOAD_COMPLETED,
  LS_SINGLE_IMPORT,
  IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
  AIRDCPP_TRANSFERS_FETCHED,
  LIBRARY_ISSUE_BUNDLES,
  AIRDCPP_SOCKET_CONNECTED,
  AIRDCPP_SOCKET_DISCONNECTED,
} from "../constants/action-types";
import { isNil } from "lodash";
import axios, { AxiosResponse } from "axios";

import type { AirDCPPSearchData } from "../types";
import type { Dispatch } from "react";

/** Redux action type for AirDC++ actions */
interface AirDCPPAction {
  type: string;
  data?: unknown;
  downloadResult?: unknown;
  bundleDBImportResult?: AxiosResponse | null;
  bundles?: AirDCPPBundle[];
  issue_bundles?: AxiosResponse;
  comicBookDetail?: unknown;
  IMS_inProgress?: boolean;
}

/** AirDC++ Bundle type */
interface AirDCPPBundle {
  id: string;
  name?: string;
  [key: string]: unknown;
}

/** Download item in acquisition data */
interface AirDCPPDownloadItem {
  bundleId: string;
  [key: string]: unknown;
}

/** Thunk dispatch type */
type ThunkDispatch = Dispatch<AirDCPPAction>;

/**
 * Creates a promise that resolves after a specified delay.
 * Useful for rate limiting or adding delays between operations.
 *
 * @param {number} ms - Number of milliseconds to sleep
 * @returns {Promise<NodeJS.Timeout>} Promise that resolves after the delay
 * @example
 * await sleep(1000); // Wait 1 second
 */
export const sleep = (ms: number): Promise<NodeJS.Timeout> => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

/**
 * Redux thunk action creator to toggle AirDC++ socket connection status.
 * Dispatches connection or disconnection events to update application state.
 *
 * @param {String} status - Connection status ("connected" or "disconnected")
 * @param {any} [payload] - Optional payload data for the status change
 * @returns {Function} Redux thunk function
 */
export const toggleAirDCPPSocketConnectionStatus =
  (status: String, payload?: unknown) => async (dispatch: ThunkDispatch) => {
    switch (status) {
      case "connected":
        dispatch({
          type: AIRDCPP_SOCKET_CONNECTED,
          data: payload,
        });
        break;

      case "disconnected":
        dispatch({
          type: AIRDCPP_SOCKET_DISCONNECTED,
          data: payload,
        });
        break;

      default:
        break;
    }
  };

/**
 * Redux thunk action creator to download an item from AirDC++ search results.
 * Initiates the download, stores bundle metadata in the database, and updates
 * the comic book record with download information.
 *
 * @param {Number} searchInstanceId - ID of the active search instance
 * @param {String} resultId - ID of the search result to download
 * @param {String} comicObjectId - ID of the comic book in the database
 * @param {String} name - Name of the file to download
 * @param {Number} size - Size of the file in bytes
 * @param {any} type - File type information
 * @param {any} ADCPPSocket - AirDC++ socket connection instance
 * @param {any} credentials - Authentication credentials for AirDC++
 * @returns {Function} Redux thunk function
 * @throws {Error} If download initiation or database update fails
 */
export const downloadAirDCPPItem =
  (
    searchInstanceId: Number,
    resultId: String,
    comicObjectId: String,
    name: String,
    size: Number,
    type: unknown,
    ADCPPSocket: { isConnected: () => boolean; connect: () => Promise<void>; post: (url: string) => Promise<{ bundle_info: { id: string } }> },
    credentials: unknown,
  ) =>
  async (dispatch: ThunkDispatch) => {
    try {
      if (!ADCPPSocket.isConnected()) {
        await ADCPPSocket.connect();
      }
      let bundleDBImportResult: AxiosResponse | null = null;
      const downloadResult = await ADCPPSocket.post(
        `search/${searchInstanceId}/results/${resultId}/download`,
      );

      if (!isNil(downloadResult)) {
        bundleDBImportResult = await axios({
          method: "POST",
          url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
          headers: {
            "Content-Type": "application/json; charset=utf-8",
          },
          data: {
            bundleId: downloadResult.bundle_info.id,
            comicObjectId,
            name,
            size,
            type,
          },
        });

        dispatch({
          type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
          downloadResult,
          bundleDBImportResult,
        });

        dispatch({
          type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
          comicBookDetail: bundleDBImportResult?.data,
          IMS_inProgress: false,
        });
      }
    } catch (error) {
      throw error;
    }
  };

/**
 * Redux thunk action creator to fetch AirDC++ download bundles for a specific comic.
 * Retrieves the comic book record, extracts associated bundle IDs, and fetches
 * bundle details from the AirDC++ queue.
 *
 * @param {string} comicObjectId - ID of the comic book in the database
 * @param {any} ADCPPSocket - AirDC++ socket connection instance
 * @param {any} credentials - Authentication credentials for AirDC++
 * @returns {Function} Redux thunk function that dispatches AIRDCPP_BUNDLES_FETCHED
 * @throws {Error} If fetching comic or bundles fails
 */
export const getBundlesForComic =
  (comicObjectId: string, ADCPPSocket: { isConnected: () => boolean; connect: () => Promise<void>; get: (url: string) => Promise<AirDCPPBundle> }, credentials: unknown) =>
  async (dispatch: ThunkDispatch) => {
    try {
      if (!ADCPPSocket.isConnected()) {
        await ADCPPSocket.connect();
      }
      const comicObject = await axios({
        method: "POST",
        url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
        headers: {
          "Content-Type": "application/json; charset=utf-8",
        },
        data: {
          id: `${comicObjectId}`,
        },
      });
      // get only the bundles applicable for the comic
      if (comicObject.data.acquisition.directconnect) {
        const filteredBundles =
          comicObject.data.acquisition.directconnect.downloads.map(
            async ({ bundleId }: AirDCPPDownloadItem) => {
              return await ADCPPSocket.get(`queue/bundles/${bundleId}`);
            },
          );
        dispatch({
          type: AIRDCPP_BUNDLES_FETCHED,
          bundles: await Promise.all(filteredBundles),
        });
      }
    } catch (error) {
      throw error;
    }
  };

/**
 * Redux thunk action creator to fetch all active transfers from AirDC++.
 * Retrieves current download bundles and groups library issues by their
 * associated bundle IDs for display in the UI.
 *
 * @param {any} ADCPPSocket - AirDC++ socket connection instance
 * @param {any} credentials - Authentication credentials for AirDC++
 * @returns {Function} Redux thunk function that dispatches AIRDCPP_TRANSFERS_FETCHED
 *                     and LIBRARY_ISSUE_BUNDLES actions
 * @throws {Error} If fetching transfers fails
 */
export const getTransfers =
  (ADCPPSocket: { isConnected: () => boolean; connect: () => Promise<void>; get: (url: string, options: Record<string, unknown>) => Promise<AirDCPPBundle[]> }, credentials: unknown) => async (dispatch: ThunkDispatch) => {
    try {
      if (!ADCPPSocket.isConnected()) {
        await ADCPPSocket.connect();
      }
      const bundles = await ADCPPSocket.get("queue/bundles/1/85", {});
      if (!isNil(bundles)) {
        dispatch({
          type: AIRDCPP_TRANSFERS_FETCHED,
          bundles,
        });
        const bundleIds = bundles.map((bundle: AirDCPPBundle) => bundle.id);
        // get issues with matching bundleIds
        const issue_bundles = await axios({
          url: `${SEARCH_SERVICE_BASE_URI}/groupIssuesByBundles`,
          method: "POST",
          data: { bundleIds },
        });
        dispatch({
          type: LIBRARY_ISSUE_BUNDLES,
          issue_bundles,
        });
      }
    } catch (err) {
      throw err;
    }
  };
