import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils";
import { HolidaysResult } from "../holidays";
import {
  LeaveEntry,
  RecordLeaveInput,
  RecordTimesheetInput,
  TimeEntry,
} from "./actions";
import { convertDateArrayToString } from "@/app/utils/formatUtil";
import compact from "lodash/compact";

export type TimeEntryError = {
  [field in keyof TimeEntry]?: string;
};

/**
 * @param entry - the time entry
 * @returns an object where the keys are the error fields and the values the error message, and undefined if there are no errors
 */
export const validateTimeEntry = (
  entry: Partial<TimeEntry>,
  isHoliday: boolean,
): TimeEntryError | undefined => {
  // Test for errors
  const error: TimeEntryError = {};

  // Either normal or other hours need to be inputted
  if (!entry.inputHours && !entry.otHours) {
    error[isHoliday ? "otHours" : "inputHours"] = "Required";
  } else if (entry.inputHours && isHoliday) {
    error.inputHours = "Cannot input normal hours for holidays";
  } else if (entry.inputHours && entry.inputHours <= 0) {
    error.inputHours =
      "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}";
  } else if (entry.inputHours && entry.inputHours > DAILY_NORMAL_MAX_HOURS) {
    error.inputHours =
      "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}";
  } else if (entry.otHours && entry.otHours <= 0) {
    error.otHours = "Hours should be bigger than 0";
  }

  // If there is a project id, there should also be taskGroupId, taskId, inputHours
  if (entry.projectId) {
    if (!entry.taskGroupId) {
      error.taskGroupId = "Required";
    } else if (!entry.taskId) {
      error.taskId = "Required";
    }
  } else {
    if (!entry.remark) {
      error.remark = "Required for non-billable tasks";
    }
  }

  return Object.keys(error).length > 0 ? error : undefined;
};

export const isValidLeaveEntry = (entry: Partial<LeaveEntry>): string => {
  // Test for errrors
  let error: keyof LeaveEntry | "" = "";
  if (!entry.leaveTypeId) {
    error = "leaveTypeId";
  } else if (!entry.inputHours || !(entry.inputHours >= 0)) {
    error = "inputHours";
  }

  return error;
};

export const validateTimesheet = (
  timesheet: RecordTimesheetInput,
  leaveRecords: RecordLeaveInput,
  companyHolidays: HolidaysResult[],
): { [date: string]: string } | undefined => {
  const errors: { [date: string]: string } = {};

  const holidays = new Set(
    compact([
      ...getPublicHolidaysForNYears(2).map((h) => h.date),
      ...companyHolidays.map((h) => convertDateArrayToString(h.date)),
    ]),
  );

  Object.keys(timesheet).forEach((date) => {
    const timeEntries = timesheet[date];

    // Check each entry
    for (const entry of timeEntries) {
      const entryErrors = validateTimeEntry(entry, holidays.has(date));

      if (entryErrors) {
        errors[date] = "There are errors in the entries";
        return;
      }
    }

    // Check total hours
    const leaves = leaveRecords[date];
    const leaveHours =
      leaves?.reduce((acc, entry) => acc + entry.inputHours, 0) || 0;

    const totalInputHours = timeEntries.reduce((acc, entry) => {
      return acc + (entry.inputHours || 0);
    }, 0);

    const totalOtHours = timeEntries.reduce((acc, entry) => {
      return acc + (entry.otHours || 0);
    }, 0);

    if (totalInputHours + leaveHours > DAILY_NORMAL_MAX_HOURS) {
      errors[date] =
        "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours or decrease the leave hours.";
    } else if (
      totalInputHours + totalOtHours + leaveHours >
      TIMESHEET_DAILY_MAX_HOURS
    ) {
      errors[date] =
        "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}";
    }
  });

  return Object.keys(errors).length > 0 ? errors : undefined;
};

export const validateLeaveRecord = (
  leaveRecords: RecordLeaveInput,
  timesheet: RecordTimesheetInput,
): { [date: string]: string } | undefined => {
  const errors: { [date: string]: string } = {};

  Object.keys(leaveRecords).forEach((date) => {
    const leaves = leaveRecords[date];

    // Check each leave entry
    for (const entry of leaves) {
      const entryError = isValidLeaveEntry(entry);
      if (entryError) {
        errors[date] = "There are errors in the entries";
      }
    }

    // Check total hours
    const timeEntries = timesheet[date] || [];

    const leaveHours = leaves.reduce((acc, entry) => acc + entry.inputHours, 0);

    const totalInputHours = timeEntries.reduce((acc, entry) => {
      return acc + (entry.inputHours || 0);
    }, 0);

    const totalOtHours = timeEntries.reduce((acc, entry) => {
      return acc + (entry.otHours || 0);
    }, 0);

    if (totalInputHours + leaveHours > DAILY_NORMAL_MAX_HOURS) {
      errors[date] =
        "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours or decrease the leave hours.";
    } else if (
      totalInputHours + totalOtHours + leaveHours >
      TIMESHEET_DAILY_MAX_HOURS
    ) {
      errors[date] =
        "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}";
    }
  });

  return Object.keys(errors).length > 0 ? errors : undefined;
};

export const DAILY_NORMAL_MAX_HOURS = 8;
export const LEAVE_DAILY_MAX_HOURS = 8;
export const TIMESHEET_DAILY_MAX_HOURS = 20;
