import { useMutation, useQuery } from "@apollo/client";
import { Form, Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
import { GET_JOB_BY_PK, QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import TaskUpsertModalComponent from "./task-upsert-modal.component";
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
import { useLocation, useNavigate } from "react-router-dom";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
import { isEqual } from "lodash";
import refetchRouteMappings from "./task-upsert-modal.route.mappings";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";

const mapStateToProps = createStructuredSelector({
  currentUser: selectCurrentUser,
  bodyshop: selectBodyshop,
  taskUpsert: selectTaskUpsert
});
const mapDispatchToProps = (dispatch) => ({
  toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
  insertAuditTrail: ({ jobid, billid, operation, type }) =>
    dispatch(insertAuditTrail({ jobid, billid, operation, type }))
});

export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
  const { t } = useTranslation();
  const history = useNavigate();
  const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
  const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
  const { open, context } = taskUpsert;
  const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query, view } = context;
  const [form] = Form.useForm();
  const [selectedJobId, setSelectedJobId] = useState(null);
  const [selectedJobDetails, setSelectedJobDetails] = useState(null);
  const [jobIdState, setJobIdState] = useState(null);
  const [isTouched, setIsTouched] = useState(false);
  const location = useLocation();
  const notification = useNotification();

  const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
    variables: { id: jobIdState },
    skip: !jobIdState
  });

  const {
    loading: taskLoading,
    error: taskError,
    data: taskData
  } = useQuery(QUERY_GET_TASK_BY_ID, {
    variables: { id: taskId },
    skip: !taskId
  });
  // Use Effect to hydrate existing task if only a taskid is provided
  useEffect(() => {
    if (!taskLoading && !taskError && taskData && taskData?.tasks_by_pk) {
      form.setFieldsValue(taskData.tasks_by_pk);
      setSelectedJobId(taskData.tasks_by_pk.jobid);
    }
  }, [taskLoading, taskError, taskData, form]);

  // Use Effect to hydrate selected job details
  useEffect(() => {
    if (!loading && !error && data) {
      setSelectedJobDetails(data.jobs_by_pk);
    }
  }, [loading, error, data]);

  // Use Effect to toggle to set jobid state
  useEffect(() => {
    if (selectedJobId) {
      setJobIdState(selectedJobId);
    }
  }, [selectedJobId]);

  // Use Effect to hydrate form fields
  useEffect(() => {
    if (jobid || existingTask?.id) {
      setSelectedJobId(jobid || existingTask.jobid);
    }
    if (existingTask && open) {
      form.setFieldsValue(existingTask);
    } else if (!existingTask && open) {
      form.resetFields();
      if (joblineid) form.setFieldsValue({ joblineid });
      if (billid) form.setFieldsValue({ billid });
      if (partsorderid) form.setFieldsValue({ partsorderid });
    }
    return () => {
      setSelectedJobId(null);
      setIsTouched(false);
    };
  }, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);

  /**
   * Remove the taskid from the URL
   */
  const removeTaskIdFromUrl = () => {
    const urlParams = new URLSearchParams(window.location.search);
    if (!urlParams.has("taskid")) return;
    urlParams.delete("taskid");
    history(`${window.location.pathname}?${urlParams}`);
  };

  /**
   * Generate refetch queries
   * @param jobId
   * @returns {*[]}
   */
  const generateRefetchQueries = (jobId) => {
    const refetchQueries = [];

    if (location.pathname.includes("/manage/jobs") && jobId) {
      refetchQueries.push({
        query: GET_JOB_BY_PK,
        variables: {
          id: jobId
        }
      });
    }

    // We have a specified query
    if (query && Object.keys(query).length) {
      refetchQueries.push(Object.keys(query)[0]);
    }
    // We don't have a specified query, check the page
    else {
      refetchRouteMappings.forEach((mapping) => {
        if (location.pathname.includes(mapping.route)) {
          refetchQueries.push(mapping.query);
        }
      });
    }

    return refetchQueries;
  };

  /**
   * Handle existing task
   * @param id
   * @param jobId
   * @param values
   * @returns {Promise<void>}
   */
  const handleExistingTask = async (id, jobId, values) => {
    const task = replaceUndefinedWithNull(values);

    // Remind at is dirty so lets clear remind_at_sent
    if (task?.remind_at) {
      task.remind_at_sent = null;
    }

    const taskObject = {
      variables: {
        taskId: id,
        task
      }
    };

    taskObject.refetchQueries = generateRefetchQueries(jobId);

    const taskData = await updateTask(taskObject);

    if (!taskData.errors) {
      const oldTask = taskData?.data?.update_tasks?.returning[0];

      insertAuditTrail({
        jobid: oldTask.jobid,
        operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
        type: "tasksUpdated"
      });
    }

    notification["success"]({
      message: t("tasks.successes.updated")
    });

    toggleModalVisible();
  };

  /**
   * Handle new task
   * @param values
   * @returns {Promise<void>}
   */
  const handleNewTask = async (values) => {
    const taskObject = {
      variables: {
        taskInput: [
          {
            ...values,
            created_by: currentUser.email,
            bodyshopid: bodyshop.id
          }
        ]
      }
    };

    taskObject.refetchQueries = generateRefetchQueries(values.jobid);

    const newTaskData = await insertTask(taskObject);
    const newTask = newTaskData?.data?.insert_tasks?.returning[0];

    if (!newTaskData.errors) {
      insertAuditTrail({
        jobid: newTask.jobid,
        operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
        type: "tasksCreated"
      });
    }

    form.resetFields();
    toggleModalVisible();

    notification["success"]({
      message: t("tasks.successes.created")
    });
  };

  /**
   * Handle the form submit
   * @param formValues
   * @returns {Promise<[{jobid, bodyshopid, created_by},...*]>}
   */
  const handleFinish = async (formValues) => {
    if (existingTask || taskData?.tasks_by_pk) {
      const taskSource = existingTask || taskData?.tasks_by_pk;
      const dirtyValues = Object.keys(formValues).reduce((acc, key) => {
        if (!isEqual(formValues[key], taskSource[key])) {
          acc[key] = formValues[key];
        }
        return acc;
      }, {});
      try {
        await handleExistingTask(taskSource.id, taskSource.jobid, dirtyValues);
      } catch (e) {
        notification["error"]({
          message: t("tasks.failures.updated")
        });
      }
    } else {
      try {
        await handleNewTask(formValues);
      } catch (e) {
        notification["error"]({
          message: t("tasks.failures.created")
        });
      }
    }
  };

  return (
    <Modal
      title={
        <span id="task-upsert-modal-title">
          {view ? t("tasks.actions.view") : existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
        </span>
      }
      open={open}
      okText={t("general.actions.save")}
      width="50%"
      cancelText={!isTouched ? t("general.actions.ok") : t("general.actions.cancel")}
      onOk={() => {
        removeTaskIdFromUrl();
        form.submit();
      }}
      onCancel={() => {
        removeTaskIdFromUrl();
        toggleModalVisible();
      }}
      okButtonProps={{ disabled: !isTouched }}
      destroyOnClose
    >
      <Form
        form={form}
        onFinish={handleFinish}
        layout="vertical"
        onFieldsChange={() => {
          setIsTouched(true);
        }}
      >
        <TaskUpsertModalComponent
          form={form}
          loading={loading || (taskId && taskLoading)}
          error={error}
          data={data}
          view={view}
          existingTask={existingTask || taskData?.tasks_by_pk}
          selectedJobId={selectedJobId}
          setSelectedJobId={setSelectedJobId}
          selectedJobDetails={selectedJobDetails}
        />
      </Form>
    </Modal>
  );
}

export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);
