import { DMap } from "./DMap";
import Database from "../../../../../Components/common/DAsyncStorage";
import NetInfo from "@react-native-community/netinfo";
import CONFIGS from "../SilentUpload/NotificationsConfig";
import {
  updateNotificationContent,
  createDefaultNotificationConfig,
} from "../SilentUpload/BackgroundTasksUtils";
import {
  createPromisifiedTask,
  startBackgroundProcess,
} from "../SilentUpload/BackgroundTasks";
import BackgroundService from "react-native-background-actions";
import UploadFile from "../UploadFile.js";
import store from "../../redux/store";

// Experimental DocumentLast call
import { BackgroundDocumentLast } from "../SilentUpload/BackgroundDocumentLast";
import {
  canGoVideoPi,
  is2Wflow,
  isCVFlow,
} from "react-native-digitcamera/src/common/Constants";

//keep track of what is going for the upload
//to upload a document from whereever in the app
//just initialize the document status to pending in the mediaUpload of that activity
//and the background service interval will fetch it and put it into the _UPLOADS
//which then access for the interval and again puts it into _SENTUPLOADS to make sure we don't upload duplicates
//and then uploads the documents
const _UPLOADS = new DMap();

//keep track of what went for the uploads
const _SENTUPLOADS = new DMap();

//allowed upload retry count
const _ALLOWED_UPLOAD_RETRIES = 2;

//allow last call retry count
const _ALLOWED_LAST_CALL_RETRIES = 2;

//keep track of what is going for the last call
const _LASTCALLS = new DMap();

//keep track of what went for last call
const _SENTLASTCALLS = new DMap();

/**
 * to make sure that we don't upload duplicates
 * taking 5000ms to fetch another pending upload
 * its good enough, because changing status of the upload from pending to uploading will take less time than 5000ms
 * hence no duplications will occur
 */
const _FETCH_INTERVAL_VALUE = 5000;

/**
 * if there is no activity by user for 5 min straight kill the background process
 * or we can decide the timeout value later
 */
const _RESOLVE_TIMEOUT = 5 * 60000;

//set to private and global for the class
//so it can be clear and unsubscribed from any function
let _FETCH_INTERVAL = null;
let _NETWORK_LISTENER = null;

/**
 * runs a interval inside a NetInfo listener
 * checks if there are any incoming uploads
 * checks if there are any pending last calls
 */
export const start = () => {
  // for testing
  if (BackgroundService.isRunning()) {
    BackgroundService.stop();
  }

  // Run this only when there is no existing background service running
  if (!BackgroundService.isRunning()) {
    const uploadTask = createPromisifiedTask((resolve, reject) => {
      backgroundTask(resolve, reject);
    });
    const notificationConfig = createDefaultNotificationConfig(100);
    startBackgroundProcess(uploadTask, notificationConfig);
  } else {
    log("Error Cannot start Silent Upload, its already running");
  }
};

const backgroundTask = () => {
  _NETWORK_LISTENER = NetInfo.addEventListener((state) => {
    if (state.isInternetReachable === true) {
      //let user know app can access internet
      updateNotificationContent(CONFIGS.BG_KILLED_NETWORK_ACCESS);

      //after 5 sec, show document uploading
      setTimeout(() => {
        updateNotificationContent(
          CONFIGS.getCustomConfig({
            tastName: "Welcome user",
            taskTitle: "Welcome",
            taskDesc:
              "We will upload photos/videos as soon as you capture them",
          })
        );
      }, 5000);

      //run the interval every 5 seconds to fetch pending uploads and last calls
      _FETCH_INTERVAL = setInterval(async () => {
        //fetch pending uploads
        fetchForUpload();

        log("running..");
        //log updated _UPLOADS map
        log("checking for pending uploads", _UPLOADS);

        //upload each pending item
        _UPLOADS.forEach((uploadItem) => {
          upload(uploadItem);
        });

        //fetch for last call pending
        fetchLastPending();

        //log updated _LASTCALLS map
        log("checking for pending last calls", _LASTCALLS);

        //make last call for pending activities
        callLastService();
      }, _FETCH_INTERVAL_VALUE);
    } else if (state.isInternetReachable === false) {
      //clear the existing interval
      //so that once internet is reachable we can start new one and old one does not exist
      log("clearing the fetch interval");
      clearInterval(_FETCH_INTERVAL);

      //let the user know that is app is not able to connect to the internet
      updateNotificationContent(CONFIGS.BG_KILLED_NETWORK_FAILURE);
    }
  });
};

/**
 * fetchs media for uploading
 * */
const fetchForUpload = async () => {
  try {
    //get latest mediaStatus
    const pMediaStatus = await Database.get("PIMediaStatus");
    const AppLoginStatus = await Database.get("AppLoginStatus");
    const USERTYPE = AppLoginStatus?.userType;

    if (pMediaStatus && pMediaStatus instanceof Array) {
      //get inProgressArray
      const inProgressArray = pMediaStatus.filter(
        (item) =>
          item.activityStatus != "COMPLETED" &&
          !item.activityId.toString().includes("Offline_")
      );

      if (inProgressArray && inProgressArray instanceof Array) {
        // find mediaUpload item,whose status is uploading
        // and put it in _UPLOADS map
        inProgressArray.forEach((item) => {
          if (
            canGoVideoPi(item.activityTypeId) ||
            isCVFlow(item.activityTypeId) ||
            AppLoginStatus?.userType == "OEMDealer"
          ) {
            log(
              "DSilentUpload.js : fetchForUpload : activityTypeId : canGoVideoPi(item.activityTypeId) || isCVFlow(item.activityTypeId): ",
              item.activityTypeId,
              canGoVideoPi(item.activityTypeId),
              isCVFlow(item.activityTypeId)
            );
            getDataForUpload(item);
          } else {
            log("fetchForUpload : 2W flow :", item.activityTypeId);
            getDataForUpload(item);
          }
        });
      } else {
        log(
          "INFO - fetchForUpload - inProgressArray is undefined or not an array"
        );
      }
    } else {
      log("INFO - fetchForUpload - pMediaStatus is undefined / not an array");
    }
  } catch (error) {
    log("Error catch :fetchForUpload() ", error);
  }
};

const getDataForUpload = (item) => {
  try {
    if (item.mediaUpload && item.mediaUpload instanceof Array) {
      item.mediaUpload.forEach(async (muItem) => {
        let PIRetryCount = await Database.get("PIRetryCount");

        const activityId = item.activityId;
        const activityTypeId = item.activityTypeId;
        const imageName = muItem.imageName;

        //add pending item in _UPLOADS
        if (muItem.status === "pending") {
          var DocToUpload = new Set(["VDO", "SF", "CNG", "IWS", "PUC"]);
          if (
            (is2Wflow(activityTypeId) && DocToUpload.has(imageName)) ||
            imageName.includes("RCB")
          ) {
            log("is2Wflow : Pending  DocToUpload.has(imageName) ");
            const cacheKey = `${activityId}_${imageName}`;
            const exist = _UPLOADS.has(cacheKey, muItem);
            if (!exist) {
              const newObj = {
                ...muItem,
                activityId: activityId,
                retryCount: 0, //initialize pending upload retryCount as zero, so once failed that wil be increased
              };
              _UPLOADS.set(cacheKey, newObj);
            }
          } else {
            if (is2Wflow(activityTypeId) && PIRetryCount?.retryCount > 0) {
              log("is2Wflow : Pending");

              const cacheKey = `${activityId}_${imageName}`;
              const exist = _UPLOADS.has(cacheKey, muItem);
              if (!exist) {
                const newObj = {
                  ...muItem,
                  activityId: activityId,
                  retryCount: 0, //initialize pending upload retryCount as zero, so once failed that wil be increased
                };
                _UPLOADS.set(cacheKey, newObj);
              }
            } else {
              if (!is2Wflow(activityTypeId)) {
                log("CV and 4W : Pending ");
                // for CV and 4W
                const cacheKey = `${activityId}_${imageName}`;
                const exist = _UPLOADS.has(cacheKey, muItem);
                if (!exist) {
                  const newObj = {
                    ...muItem,
                    activityId: activityId,
                    retryCount: 0, //initialize pending upload retryCount as zero, so once failed that wil be increased
                  };
                  _UPLOADS.set(cacheKey, newObj);
                }
              }
            }
          }
        }

        /**
         * Find failure uploads
         * if it exists that means it was failed.
         * it should only work when status of each upload consistently changes in each operation
         * lets say for first time it uploads the status is uploading
         * if it fails the status is failed
         * once started uploading again the status should be uploading instead of failed
         * otherwise it will create infinite duplicates of failed uploads
         */
        if (muItem.status === "failed") {
          var DocToUpload = new Set(["VDO", "SF", "CNG", "IWS", "PUC"]);
          if (
            (is2Wflow(activityTypeId) && DocToUpload.has(imageName)) ||
            imageName.includes("RCB")
          ) {
            log("is2Wflow : failed  DocToUpload.has(imageName) ", imageName);

            const cacheKey = `${activityId}_${imageName}`;
            // should always exist in this map
            // because item should be atleast tried uploading one time
            // before failing
            const exist = _UPLOADS.has(cacheKey);
            if (exist) {
              const oldObj = _UPLOADS.get(cacheKey);
              if (oldObj.retryCount < _ALLOWED_UPLOAD_RETRIES) {
                const newObj = {
                  ...oldObj,
                  retryCount: oldObj.retryCount + 1,
                };
                _UPLOADS.update(cacheKey, newObj);
              } else {
                log(
                  `cannot retry uploading of ${cacheKey} for more than ${_ALLOWED_UPLOAD_RETRIES} times`
                );
              }
            }
          } else {
            if (is2Wflow(activityTypeId) && PIRetryCount?.retryCount > 0) {
              log("is2Wflow : failed");
              const cacheKey = `${activityId}_${imageName}`;
              // should always exist in this map
              // because item should be atleast tried uploading one time
              // before failing
              const exist = _UPLOADS.has(cacheKey);
              if (exist) {
                const oldObj = _UPLOADS.get(cacheKey);
                if (oldObj.retryCount < _ALLOWED_UPLOAD_RETRIES) {
                  const newObj = {
                    ...oldObj,
                    retryCount: oldObj.retryCount + 1,
                  };
                  _UPLOADS.update(cacheKey, newObj);
                } else {
                  log(
                    `cannot retry uploading of ${cacheKey} for more than ${_ALLOWED_UPLOAD_RETRIES} times`
                  );
                }
              }
            } else {
              if (!is2Wflow(activityTypeId)) {
                log("cv and 4w : failed");
                const cacheKey = `${activityId}_${imageName}`;
                // should always exist in this map
                // because item should be atleast tried uploading one time
                // before failing
                const exist = _UPLOADS.has(cacheKey);
                if (exist) {
                  const oldObj = _UPLOADS.get(cacheKey);
                  if (oldObj.retryCount < _ALLOWED_UPLOAD_RETRIES) {
                    const newObj = {
                      ...oldObj,
                      retryCount: oldObj.retryCount + 1,
                    };
                    _UPLOADS.update(cacheKey, newObj);
                  } else {
                    log(
                      `cannot retry uploading of ${cacheKey} for more than ${_ALLOWED_UPLOAD_RETRIES} times`
                    );
                  }
                }
              }
            }
          }
        }

        // if already uploaded remove the item from _UPLOADS map
        if (muItem.status === "uploaded") {
          const cacheKey = `${activityId}_${imageName}`;
          const found = _UPLOADS.has(cacheKey);
          if (found) {
            log(`deleting ${cacheKey} from _UPLOADS as its uploaded`);
            _UPLOADS.delete(cacheKey);
          }
        }
      });
    } else {
      log(
        "Error - fetchForUpload - item.mediaUpload is undefined or not an array"
      );
    }
  } catch (error) {
    log("Error : catch getDataForUpload()");
  }
};

const upload = (uploadItem) => {
  const { activityId, imageName, uri, retryCount } = uploadItem;
  const keyToFind = `${activityId}_${imageName}`;

  /**
   * we only delete items from _UPLOADS when the existing item, is already uploaded
   */
  const exist = _SENTUPLOADS.get(keyToFind);
  if (!exist) {
    _SENTUPLOADS.set(keyToFind, uploadItem);
    execUploadFile(activityId, uri, imageName);
  } else {
    //if exist then updates only which are failed
    //take retryCount and check if its greater and equals to one
    if (retryCount > 0) {
      if (retryCount < _ALLOWED_UPLOAD_RETRIES) {
        _SENTUPLOADS.update(keyToFind, uploadItem);
        execUploadFile(activityId, uri, imageName);
      } else {
        log(
          `cannot reupload again ${keyToFind} because retryCount ${retryCount} exceded allowed retries ${_ALLOWED_UPLOAD_RETRIES}`
        );
      }
    } else {
      log(`cannot reupload again ${keyToFind}`);
    }
  }
};

const execUploadFile = (activityId, uri, imageName) => {
  const ext = imageName === "VDO" ? ".mp4" : ".jpg";

  store.dispatch({
    type: "ADD_UPLOAD_PROGRESS_DATA",
    payload: {
      activityId: activityId,
      imageName: imageName,
      progress: 0,
      uploadSpeed: 0,
      secondsRemaining: 0,
    },
  });

  setTimeout(() => {
    UploadFile(
      uri,
      imageName,
      ext,
      () => {
        // ON FAIL UPLOAD CALLBACK
      },
      () => {
        addSensorImageData(ext, true, imageName);
      },
      () => {
        addSensorImageData(ext, false, imageName);
      }
    );
  }, 2000);
};

// Fetch activities whose uploads are done
// and required to hit the last document service
const fetchLastPending = async () => {
  const pMediaStatus = await Database.get("PIMediaStatus");

  if (pMediaStatus && pMediaStatus instanceof Array) {
    //get inProgressArray
    const inProgressArray = pMediaStatus.filter(
      (ipaItem) => ipaItem.activityStatus === "IN-PROGRESS"
    );

    if (inProgressArray && inProgressArray instanceof Array) {
      inProgressArray.forEach((item) => {
        if (item.mediaUpload && item.mediaUpload instanceof Array) {
          const allUploaded = item.mediaUpload.every(
            (item) => item.status === "uploaded"
          );
          const videoExist = item.mediaUpload.find(
            (item) => item.imageName === "VDO"
          );
          if (allUploaded && videoExist) {
            const cacheKey = `${item.activityId}`;
            const exist = _LASTCALLS.has(cacheKey);
            if (!exist) {
              _LASTCALLS.set(cacheKey, item);
            }
          }
        } else {
          log(
            "INFO - fetchLastPending - item.mediaUpload is undefined or not an array"
          );
        }
      });
    } else {
      log(
        "INFO - fetchLastPending - inProgressArray is undefined or not an array"
      );
    }
  } else {
    log("INFO - fetchLastPending - pMediaStatus is undefined or not an array");
  }
};
const callLastService = () => {
  log("pending last calls ", _LASTCALLS);
  //call backgrounddocumentlast
  _LASTCALLS.forEach((item) => {
    const keyToFind = `${item.activityId}`;
    const exist = _SENTLASTCALLS.has(keyToFind);
    if (!exist) {
      _SENTLASTCALLS.set(keyToFind, item);
      BackgroundDocumentLast(
        {
          activityID: item.activityId,
          activityTypeId: item.activityTypeId,
          licensePlateNumber: item.regNumber ?? "",
          quoteNumber: item.policyNumber ?? "",
        },
        () => {
          console.log("TEST RESOLVER");
        }
      );
    }
  });
};

const end = async () => {
  if (BackgroundService.isRunning()) {
    /**
     * There multiple cases where ending the background service is necessary
     * 1. If there is even one activity IN-PROGRESS.
     */
    const pMediaStatus = await Database.get("PIMediaStatus");

    if (pMediaStatus && pMediaStatus instanceof Array) {
      const everyInProgress = pMediaStatus.some(
        (item) => item.activityStatus === "IN-PROGRESS"
      );
      if (!everyInProgress) {
        log(
          "INFO - there are no in-progress activities so stopping the background process."
        );
        endBackgrounService();
      } else {
        log(
          "activities are in IN-PROGRESS currently, can not end the background service"
        );
      }
    } else {
      endBackgrounService();
      log("INFO - MediaStatus is empty so stopping the background process.");
    }
  } else {
    log("INFO - No Silent Upload is currently running");
  }
};

const endBackgrounService = () => {
  if (_FETCH_INTERVAL) {
    clearInterval(_FETCH_INTERVAL);
  }

  if (_NETWORK_LISTENER) {
    //unsubscribe netinfo
    _NETWORK_LISTENER();
  }
  BackgroundService.stop();
};

// we can use this function to resolve/end/stop background process
// provide resolve from uploadTask promise and a timeout
const resolveAfter = (resolve, timeout) => {
  setTimeout(() => {
    resolve();
  }, timeout);
};

// please do not modify this
// it only logs contents while developing
// and not in production

// arguments used inside the function body
// is array of parameter thats are passed into log(....)

// we can not access arguments inside arrow function it logs the file contents
function log() {
  if (__DEV__) {
    console.log("[DSilentUpload] ", ...arguments);
  }
}

// Sensor related functions
let sensorImageDetails = [];
const addSensorImageData = async (ext, ImageStatus, DocumentImageName) => {
  const { sensorDataInfo } = store.getState().sensorImageData;
  var PIRetryCount = await Database.get("PIRetryCount");
  var PIVechicleData = await Database.get("PIVechicleData");
  const PILocation = await Database.get("PILocation");

  var attemptCount = PIRetryCount?.retryCount + 1;
  var activityId = PIVechicleData?.activityId;
  var fileName = activityId + "_" + DocumentImageName + ext;
  const latitude = PILocation?.lat;
  const longitude = PILocation?.long;
  if (sensorDataInfo.length > 0) {
    sensorImageDetails = sensorDataInfo;
  } else {
    sensorImageDetails = [];
  }

  //adding sensor data
  sensorImageDetails.push({
    attemptCount: attemptCount,
    fileName: fileName,
    latitude: latitude,
    longitude: longitude,
    pose: DocumentImageName,
    success: ImageStatus,
    timestamp: new Date().getTime(),
  });
  log("Sensor data : ", sensorImageDetails);
  store.dispatch({
    type: "SENSOR_DETAILS",
    payload: sensorImageDetails,
  });
};

export default {
  start,
  end,
};
