import dayjs from "../../utils/day";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import client from "../../utils/GraphQLClient";
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import { scheduleLoadFailure, scheduleLoadSuccess, setProblemJobs } from "./application.actions";
import ApplicationActionTypes from "./application.types";
import { logImEXEvent } from "../../firebase/firebase.utils";

export function* onCalculateScheduleLoad() {
  yield takeLatest(ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD, calculateScheduleLoad);
}

export function* calculateScheduleLoad({ payload: end }) {
  //REMINDER: dayjs.js is not immutable. Today WILL change when adjusted.
  const today = dayjs().startOf("day");
  const state = yield select();
  const buckets = state.user.bodyshop.ssbuckets;

  try {
    const result = yield client.query({
      query: QUERY_SCHEDULE_LOAD_DATA,
      variables: {
        start: today,
        end: end
      }
    });
    const { prodJobs, arrJobs, compJobs } = result.data;

    const load = {
      productionTotal: {},
      productionHours: 0
    };
    let problemJobs = [];
    //Set the current load.
    buckets.forEach((bucket) => {
      load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
    });

    prodJobs.forEach((item) => {
      //Add all of the jobs currently in production to the buckets so that we have a starting point.
      if (!item.actual_completion && dayjs(item.scheduled_completion).isBefore(dayjs().startOf("day"))) {
        problemJobs.push({
          ...item,
          code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections"
        });
      }

      if (item.actual_completion && dayjs(item.actual_completion).isBefore(dayjs().startOf("day"))) {
        problemJobs.push({
          ...item,
          code: "Job is already marked as completed, but it is still in production. This job should be removed from production"
        });
      }
      if (!(item.actual_completion || item.scheduled_completion)) {
        problemJobs.push({
          ...item,
          code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections"
        });
      }

      const bucketId = CheckJobBucket(buckets, item);
      load.productionHours =
        load.productionHours + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs;
      if (bucketId) {
        load.productionTotal[bucketId].count = load.productionTotal[bucketId].count + 1;
      } else {
        console.log("Uh oh, this job doesn't fit in a bucket!", item);
      }
    });

    arrJobs.forEach((item) => {
      if (!item.scheduled_in) {
        console.log("JOB HAS NO SCHEDULED IN DATE.", item);
        problemJobs.push({
          ...item,
          code: "Job has no scheduled in date"
        });
      }
      if (!item.actual_completion && item.actual_in && !item.inproduction) {
        problemJobs.push({
          ...item,
          code: "Job has an actual in date, but no actual completion date and is not marked as in production"
        });
      }
      if (item.actual_in && dayjs(item.actual_in).isAfter(dayjs())) {
        problemJobs.push({
          ...item,
          code: "Job has an actual in date set in the future"
        });
      }
      if (item.actual_completion && dayjs(item.actual_completion).isAfter(dayjs())) {
        problemJobs.push({
          ...item,
          code: "Job has an actual completion date set in the future"
        });
      }
      if (item.actual_completion && item.inproduction) {
        problemJobs.push({
          ...item,
          code: "Job has an actual completion date but it is still marked in production"
        });
      }

      const itemDate = dayjs(item.actual_in || item.scheduled_in).format("YYYY-MM-DD");

      const AddJobForSchedulingCalc = !item.inproduction;

      if (load[itemDate]) {
        load[itemDate].allHoursIn =
          (load[itemDate].allHoursIn || 0) +
          item.labhrs.aggregate.sum.mod_lb_hrs +
          item.larhrs.aggregate.sum.mod_lb_hrs;
        load[itemDate].allHoursInBody = (load[itemDate].allHoursInBody || 0) + item.labhrs.aggregate.sum.mod_lb_hrs;
        load[itemDate].allHoursInRefinish =
          (load[itemDate].allHoursInRefinish || 0) + item.larhrs.aggregate.sum.mod_lb_hrs;
        //If the job hasn't already arrived, add it to the jobs in list.
        // Make sure it also hasn't already been completed, or isn't an in and out job.
        //This prevents the duplicate counting.
        load[itemDate].allJobsIn.push(item);
        if (AddJobForSchedulingCalc) {
          load[itemDate].jobsIn.push(item);
          load[itemDate].hoursIn =
            (load[itemDate].hoursIn || 0) + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs;
          load[itemDate].hoursInBody = (load[itemDate].hoursInBody || 0) + item.labhrs.aggregate.sum.mod_lb_hrs;
          load[itemDate].hoursInRefinish = (load[itemDate].hoursInRefinish || 0) + item.larhrs.aggregate.sum.mod_lb_hrs;
        }
      } else {
        load[itemDate] = {
          allJobsIn: [item],
          jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
          jobsOut: [],
          allJobsOut: [],
          allHoursIn: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs,
          allHoursInBody: item.labhrs.aggregate.sum.mod_lb_hrs,
          allHoursInRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
          hoursIn: AddJobForSchedulingCalc
            ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
            : 0,
          hoursInBody: AddJobForSchedulingCalc ? item.labhrs.aggregate.sum.mod_lb_hrs : 0,
          hoursInRefinish: AddJobForSchedulingCalc ? item.larhrs.aggregate.sum.mod_lb_hrs : 0
        };
      }
    });

    compJobs.forEach((item) => {
      if (!(item.actual_completion || item.scheduled_completion)) console.warn("JOB HAS NO COMPLETION DATE.", item);

      const inProdJobs = prodJobs.find((p) => p.id === item.id);
      const inArrJobs = arrJobs.find((p) => p.id === item.id);

      const AddJobForSchedulingCalc = inProdJobs || inArrJobs;

      const itemDate = dayjs(item.actual_completion || item.scheduled_completion).format("YYYY-MM-DD");
      //Skip it, it's already completed.

      if (load[itemDate]) {
        load[itemDate].allHoursOut =
          (load[itemDate].allHoursOut || 0) +
          item.labhrs.aggregate.sum.mod_lb_hrs +
          item.larhrs.aggregate.sum.mod_lb_hrs;
        load[itemDate].allHoursOutBody = (load[itemDate].allHoursOutBody || 0) + item.labhrs.aggregate.sum.mod_lb_hrs;
        load[itemDate].allHoursOutRefinish =
          (load[itemDate].allHoursOutRefinish || 0) + item.larhrs.aggregate.sum.mod_lb_hrs;
        //Add only the jobs that are still in production to get rid of.
        //If it's not in production, we'd subtract unnecessarily.
        load[itemDate].allJobsOut.push(item);

        if (AddJobForSchedulingCalc) {
          load[itemDate].jobsOut.push(item);
          load[itemDate].hoursOut =
            (load[itemDate].hoursOut || 0) +
            item.labhrs.aggregate.sum.mod_lb_hrs +
            item.larhrs.aggregate.sum.mod_lb_hrs;
          load[itemDate].hoursOutBody = (load[itemDate].hoursOutBody || 0) + item.labhrs.aggregate.sum.mod_lb_hrs;
          load[itemDate].hoursOutRefinish =
            (load[itemDate].hoursOutRefinish || 0) + item.larhrs.aggregate.sum.mod_lb_hrs;
        }
      } else {
        load[itemDate] = {
          allJobsOut: [item],
          jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above.
          hoursOut: AddJobForSchedulingCalc
            ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
            : 0,
          allHoursOut: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs,
          allHoursOutBody: item.labhrs.aggregate.sum.mod_lb_hrs,
          allHoursOutRefinish: item.larhrs.aggregate.sum.mod_lb_hrs
        };
      }
    });

    //Propagate the expected load to each day.

    const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
    for (var day = 0; day < range; day++) {
      const current = dayjs(today).add(day, "day").format("YYYY-MM-DD");
      const prev = dayjs(today)
        .add(day - 1, "day")
        .format("YYYY-MM-DD");
      if (!load[current]) {
        load[current] = {};
      }

      if (day === 0) {
        //Starting on day 1. The load is current.
        load[current].expectedLoad = CalculateLoad(
          load.productionTotal,
          buckets,
          load[current].jobsIn || [],
          load[current].jobsOut || []
        );
        load[current].expectedJobCount =
          prodJobs.length + (load[current].jobsIn || []).length - (load[current].jobsOut || []).length;
        load[current].expectedHours =
          load.productionHours + (load[current].hoursIn || 0) - (load[current].hoursOut || 0);
      } else {
        load[current].expectedLoad = CalculateLoad(
          load[prev].expectedLoad,
          buckets,
          load[current].jobsIn || [],
          load[current].jobsOut || []
        );
        load[current].expectedJobCount =
          load[prev].expectedJobCount + (load[current].jobsIn || []).length - (load[current].jobsOut || []).length;
        load[current].expectedHours =
          load[prev].expectedHours + (load[current].hoursIn || 0) - (load[current].hoursOut || 0);
      }
    }

    yield put(setProblemJobs(problemJobs));
    yield put(scheduleLoadSuccess(load));
  } catch (error) {
    yield put(scheduleLoadFailure(error));
  }
}

export function* onInsertAuditTrail() {
  yield takeLatest(ApplicationActionTypes.INSERT_AUDIT_TRAIL, insertAuditTrailSaga);
}

export function* insertAuditTrailSaga({ payload: { jobid, billid, operation, type } }) {
  const state = yield select();
  const bodyshop = state.user.bodyshop;
  const currentUser = state.user.currentUser;

  const variables = {
    auditObj: {
      bodyshopid: bodyshop.id,
      jobid,
      billid,
      operation,
      type,
      useremail: currentUser.email
    }
  };
  yield client.mutate({
    mutation: INSERT_AUDIT_TRAIL,
    variables,
    update(cache, { data }) {
      cache.modify({
        fields: {
          audit_trail(existingAuditTrail) {
            const newAuditTrail = cache.writeQuery({
              data: data,
              query: INSERT_AUDIT_TRAIL,
              variables
            });
            return [...existingAuditTrail, newAuditTrail];
          }
        }
      });
    }
  });
}

export function* onToggleDarkMode() {
  yield takeLatest(ApplicationActionTypes.TOGGLE_DARK_MODE, function* () {
    const state = yield select();
    const darkMode = state.application.darkMode;
    logImEXEvent("dark_mode_toggled", { darkMode });
  });
}

export function* applicationSagas() {
  yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail), call(onToggleDarkMode)]);
}
