const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");

exports.job = async (req, res) => {
  const { jobId } = req.body;

  const BearerToken = req.BearerToken;
  const client = req.userGraphQLClient;

  try {
    logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);

    const result = await client
      .setHeaders({ Authorization: BearerToken })
      .request(queries.QUERY_UPCOMING_APPOINTMENTS, {
        now: moment().startOf("day"),
        jobId: jobId
      });

    const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
    const { ssbuckets, workingdays, timezone, ss_configuration } = jobs_by_pk.bodyshop;
    const jobHrs = jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;

    const JobBucket = ssbuckets.filter((bucket) => bucket.gte <= jobHrs && (bucket.lt ? bucket.lt > jobHrs : true))[0];
    const load = {
      productionTotal: {},
      productionHours: 0
    };
    //Set the current load.
    ssbuckets.forEach((bucket) => {
      load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
    });

    const filteredProdJobsList = prodJobs.filter((j) => JobBucket.id === CheckJobBucket(ssbuckets, j));

    filteredProdJobsList.forEach((item) => {
      //Add all of the jobs currently in production to the buckets so that we have a starting point.
      const bucketId = CheckJobBucket(ssbuckets, item);
      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);
      }
    });

    // const filteredArrJobs = arrJobs.filter(
    //   (j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
    // );
    const filteredArrJobs = [];

    arrJobs.forEach((item) => {
      let isSameBucket = false;
      if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
        filteredArrJobs.push(item);
        isSameBucket = true;
      }

      let jobHours = item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs;

      const AddJobForSchedulingCalc = !item.inproduction;

      const itemDate = moment(item.actual_in || item.scheduled_in)
        .tz(timezone)
        .format("yyyy-MM-DD");
      if (isSameBucket) {
        if (load[itemDate]) {
          load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc ? jobHours : 0;
          if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item);
        } else {
          load[itemDate] = {
            jobsIn: AddJobForSchedulingCalc ? [item] : [],
            jobsOut: [],
            hoursIn: AddJobForSchedulingCalc ? jobHours : 0
          };
        }
      }

      if (!load[itemDate]) {
        load[itemDate] = {
          jobsIn: [],
          jobsOut: [],
          hoursIn: 0,
          hoursInTotal: 0
        };
      }
      load[itemDate].hoursInTotal = (load[itemDate].hoursInTotal || 0) + jobHours;
    });

    //Get the completing jobs.
    const filteredCompJobs = compJobs.filter((j) => JobBucket.id === CheckJobBucket(ssbuckets, j));

    filteredCompJobs.forEach((item) => {
      const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
      const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);

      const AddJobForSchedulingCalc = inProdJobs || inArrJobs;

      const itemDate = moment(item.actual_completion || item.scheduled_completion)
        .tz(timezone)
        .format("yyyy-MM-DD");
      if (load[itemDate]) {
        load[itemDate].hoursOut =
          (load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
            ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
            : 0;
        if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
      } else {
        load[itemDate] = {
          jobsOut: AddJobForSchedulingCalc ? [item] : [],
          hoursOut: AddJobForSchedulingCalc
            ? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
            : 0
        };
      }
    });

    //Propagate the expected load to each day.
    const yesterday = moment().tz(timezone).subtract(1, "day");
    const today = moment().tz(timezone);

    const end = moment.max([
      ...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
      ...filteredCompJobs
        .map((p) => moment(p.actual_completion || p.scheduled_completion).tz(timezone))
        .filter((p) => p.isValid() && p.isAfter(yesterday)),
      moment().tz(timezone).add(15, "days")
    ]);
    const range = Math.round(moment.duration(end.add(20, "days").diff(today)).asDays());
    for (var day = 0; day < range; day++) {
      const current = moment(today).tz(timezone).add(day, "days").format("yyyy-MM-DD");
      const prev = moment(today)
        .tz(timezone)
        .add(day - 1, "days")
        .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,
          ssbuckets,
          load[current].jobsIn || [],
          load[current].jobsOut || []
        );
      } else {
        load[current].expectedLoad = CalculateLoad(
          load[prev].expectedLoad,
          ssbuckets,
          load[current].jobsIn || [],
          load[current].jobsOut || []
        );
      }
    }

    //Add in all of the blocked days.

    blockedDays.forEach((b) => {
      //Find it in the load, set it as blocked.
      const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD");
      if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
      else {
        load[startIsoFormat] = { blocked: true };
      }
    });
    // //Propose the first 10 dates where we are below target.

    const possibleDates = [];
    delete load.productionTotal;
    const loadKeys = Object.keys(load).sort((a, b) => (moment(a).isAfter(moment(b)) ? 1 : -1));

    loadKeys.forEach((loadKey) => {
      const isShopOpen = (workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) && !load[loadKey].blocked;

      let isUnderDailyTotalLimit = true;

      if (
        ss_configuration &&
        ss_configuration.dailyhrslimit &&
        ss_configuration.dailyhrslimit > 0 &&
        load[loadKey] &&
        load[loadKey].hoursInTotal &&
        load[loadKey].hoursInTotal > ss_configuration.dailyhrslimit
      ) {
        isUnderDailyTotalLimit = false;
      }

      if (
        load[loadKey].expectedLoad &&
        load[loadKey].expectedLoad[JobBucket.id] &&
        JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
        isShopOpen &&
        isUnderDailyTotalLimit
      )
        possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
    });

    if (possibleDates.length < 11) {
      res.json(possibleDates);
    } else {
      res.json(possibleDates.slice(0, 10));
    }
  } catch (error) {
    logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
      error
    });
    res.status(400).send(error);
  }
};

const dayOfWeekMapper = (numberOfDay) => {
  switch (numberOfDay) {
    case 0:
      return "sunday";
    case 1:
      return "monday";
    case 2:
      return "tuesday";
    case 3:
      return "wednesday";
    case 4:
      return "thursday";
    case 5:
      return "friday";
    case 6:
      return "saturday";
  }
};

const CheckJobBucket = (buckets, job) => {
  const jobHours = job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs;

  const matchingBucket = buckets.filter((b) => (b.gte <= jobHours && b.lt ? b.lt > jobHours : true));

  return matchingBucket[0] && matchingBucket[0].id;
};

const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
  //Add the jobs coming
  const newLoad = _.cloneDeep(currentLoad);
  jobsIn.forEach((job) => {
    const bucketId = CheckJobBucket(buckets, job);
    if (bucketId) {
      newLoad[bucketId].count = newLoad[bucketId].count + 1;
    } else {
      // console.log("[Util Arr Job]Uh oh, this job doesn't fit in a bucket!", job);
    }
  });

  jobsOut.forEach((job) => {
    const bucketId = CheckJobBucket(buckets, job);
    if (bucketId) {
      newLoad[bucketId].count = newLoad[bucketId].count - 1;
      if (newLoad[bucketId].count < 0) {
        // console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job);
      }
    } else {
      // console.log("[Util Out Job]Uh oh, this job doesn't fit in a bucket!", job);
    }
  });

  return newLoad;
};
