const log = require("../utils/logs.utils");
const db = require("./dbAccess.services");
const clean = require("../utils/sanitizer.utils");
const pis = require("../services/pis.services");
const { filter } = require("compression");

async function findByHeadcodeToday(headcode) {
  const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
  log.out(
    "trainServiceServices.findByHeadcode: Searching for headcode " +
      sanitizedHeadcode,
    "dbug"
  );
  const now = new Date();
  const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
  const shortDay = dayMap[now.getDay()]; // Fetch short day from map
  const query = {
    headcode: sanitizedHeadcode,
    scheduleStartDate: { $lte: now },
    scheduleEndDate: { $gte: now },
    daysRun: { $in: [shortDay] },
  };
  const queryData = await db.query("timetable", query);
  let trainData = await parseTrains(queryData);
  let preparedData = [];
  for (let trainService in trainData) {
    if (pis.supported.includes(trainService?.operator)) {
      // Search for PIS Code for each service
      const tiplocList = await getPublicStops(trainService?.stops);
      //console.log(tiplocList.length); console.log(tiplocList);
      if (tiplocList.length) {
        const pisDetail = await pis.findByTiplocArray(tiplocList);
        trainService["pis"] = pisDetail?.[0]?.["code"] ?? "None";
      } else if (trainService?.operator === "GW") {
        trainService["pis"] = "0015"; // Not in Service code
        // '0015' is a string becuase 0015 is not a valid number..
      }
    }
    preparedData.push(trainService);
  }
  return preparedData;
}

async function findByHeadcode(date, headcode) {
  const sanitizedHeadcode = clean.removeNonAlphanumeric(headcode).toUpperCase();
  log.out(
    "trainServiceServices.findByHeadcode: Searching for headcode " +
      sanitizedHeadcode,
    "dbug"
  );
  let searchDate;
  if (date === "now") {
    searchDate = new Date();
  } else {
    searchDate = new Date(date);
  }
  searchDate.setHours(12, 0, 0); // Set to midday to avoid any timezone issues
  const dayMap = ["su", "m", "t", "w", "th", "f", "s"];
  const shortDay = dayMap[searchDate.getDay()]; // Fetch short day from map
  const query = {
    headcode: sanitizedHeadcode,
    scheduleStartDate: { $lte: searchDate },
    scheduleEndDate: { $gte: searchDate },
    daysRun: { $in: [shortDay] },
  };
  const pipeline = [
    {
      $match: query,
    },
    {
      $project: {
        operator: 1,
        stops: {
          $concatArrays: [
            [{ $first: "$stops" }],
            [{ $arrayElemAt: ["$stops", -1] }],
          ],
        },
        trainUid: 1,
        stpIndicator: 1,
      },
    },
  ];
  const queryData = await db.queryAggregate("timetable", pipeline);
  console.log(JSON.stringify(queryData));
  let filteredData = filterServices(queryData);
  return await filteredData;
}

async function findByTrainUid(uid, date = new Date()) {
  let queryDate;
  if (date === "now") {
    queryDate = new Date();
  } else {
    queryDate = date;
  }
  const query = {
    trainUid: uid,
    scheduleStartDate: { $lte: queryDate },
    scheduleEndDate: { $gte: queryDate },
  };
  const queryData = await db.query("timetable", query);
  if (queryData.length === 0) {
    return [];
  }
  let services;
  services = await filterServices(queryData);
  console.log(services);
  let publicStops;
  if (pis.supported.includes(services[0]?.operator)) {
    publicStops = await getPublicStops(services[0]?.stops);
    if (publicStops.length) {
      const pisCode = await pis.findByTiplocArray(publicStops);
      services[0].pis = pisCode[0]?.code;
    } else if (services[0]?.operator === "GW" && !publicStops.length) {
      services[0].pis = "0015";
    }
  }
  return services[0];
}

module.exports = {
  findByHeadcodeToday,
  findByHeadcode,
  findByTrainUid,
};

/* Internal Functions, not to be exported */

/* Accepts the 'stops' array from a db query and produces an
   array of only public stops as TIPLOCs. */
async function getPublicStops(data) {
  let tiplocList = [];
  for (const publicStop in data) {
    if (data[publicStop]["isPublic"]) {
      tiplocList.push(data[publicStop]["tiploc"]);
    }
  }
  return tiplocList;
}

/* Takes a single days data from a headcode query and requeries
   using the trainUid, required to ensure any cancellations are
   accounted for */
async function parseTrains(data) {
  let trainUids = [];
  for (const i of data) {
    const trainUid = i["trainUid"];
    if (!trainUids.includes(trainUid)) {
      trainUids.push(trainUid);
    }
  }
  let parsedData = [];
  for (const i in trainUids) {
    const result = await findByTrainUid(trainUids[i]);
    parsedData.push(result);
  }
  return parsedData;
}

async function filterServices(data) {
  let stpIndicators = {},
    filteredServices = [];
  for (const serviceDetail of data) {
    const trainUid = serviceDetail["trainUid"];
    const stpIndicator = serviceDetail["stpIndicator"];
    if (!stpIndicators[trainUid]) {
      stpIndicators[trainUid] = {
        hasC: false,
        hasN: false,
        hasO: false,
        hasP: false,
      };
    }
    if (stpIndicator === "C") {
      stpIndicators[trainUid].hasC = true;
    }
    if (stpIndicator === "N") {
      stpIndicators[trainUid].hasN = true;
    }
    if (stpIndicator === "O") {
      stpIndicators[trainUid].hasO = true;
    }
    if (stpIndicator === "P") {
      stpIndicators[trainUid].hasP = true;
    }
  }
  let preparedData;
  for (const serviceDetail of data) {
    const trainUid = serviceDetail["trainUid"];
    const thisStpIndicators = stpIndicators[trainUid];
    const stpIndicator = serviceDetail["stpIndicator"];

    if (stpIndicator === "C") {
      break;
    }
    if (stpIndicator === "N" && !thisStpIndicators.hasC) {
      filteredServices.push(serviceDetail);
    } else if (
      stpIndicator === "O" &&
      !thisStpIndicators.hasC &&
      !thisStpIndicators.hasN
    ) {
      filteredServices.push(serviceDetail);
    } else if (
      stpIndicator === "P" &&
      !thisStpIndicators.hasC &&
      !thisStpIndicators.hasN &&
      !thisStpIndicators.hasO
    ) {
      filteredServices.push(serviceDetail);
    }
  }
  return filteredServices;
}
