import { moment } from '@humand-packages/common';
import {
    DEFAULT_AUTO_CLOSE_FIXED_HOURS_VALUE,
    DEFAULT_AUTO_CLOSE_FIXED_MINUTES_VALUE,
    DEFAULT_AUTO_CLOSE_LIMIT_HOURS,
    DEFAULT_AUTO_CLOSE_MODE,
} from '../constants/timeTrackingPolicy';
import { AutoCloseFallback } from '../enum/timeTracking/autoCloseFallback';
import { TimeTrackingPolicyAutoCloseMode } from '../enum/timeTracking/policy';
import { TimeTrackingDaySummary } from '../models/daySummary';
import { AutoCloseData, AutoCloseDataContext, AutoCloseFixedHoursData, AutoCloseResult } from '../models/timeTracking/autoCloseData';
import { TIME_FORMAT, TimeSlot } from '../models/timeTracking/scheduleData';
import { TimeTrackingEntryPrototype } from '../models/timeTrackingEntry';
import { TimeTrackingPolicy } from '../models/timeTrackingPolicy';
import { calculateWorkedHours, getLatestOpenStart } from './timeTrackingEntryHelper';

const computeScheduledHoursFallback = (
    estimatedHours: number,
    allEntries: TimeTrackingEntryPrototype[],
): { fallback: AutoCloseFallback | null; workedHours: number } => {
    if (!estimatedHours) return { fallback: AutoCloseFallback.SCHEDULED_HOURS_NO_PLANNING, workedHours: 0 };
    const workedHours = calculateWorkedHours(allEntries);
    if (estimatedHours - workedHours <= 0) return { fallback: AutoCloseFallback.SCHEDULED_HOURS_HOURS_COMPLETED, workedHours };
    return { fallback: null, workedHours };
};

const computeEndOfWorkdayFallback = (
    timeSlots: TimeSlot[] | null,
    dateString: string,
    timezone: string,
    openStart: TimeTrackingEntryPrototype,
): { fallback: AutoCloseFallback; slotEndOnDate?: never } | { fallback: null; slotEndOnDate: Date } => {
    if (!timeSlots?.length) return { fallback: AutoCloseFallback.END_OF_WORKDAY_NO_PLANNING };
    const sortedSlots = [...timeSlots].sort((a, b) => moment(a.startTime, TIME_FORMAT).diff(moment(b.startTime, TIME_FORMAT)));
    const lastSlot = sortedSlots[sortedSlots.length - 1];
    const endMoment = moment.tz(lastSlot.endTime, TIME_FORMAT, timezone);
    const lastSlotStart = moment.tz(lastSlot.startTime, TIME_FORMAT, timezone);
    const isNightShift = endMoment.isBefore(lastSlotStart);

    const slotBase = moment.tz(dateString, timezone);
    const slotEndMoment = slotBase.hours(endMoment.hours()).minutes(endMoment.minutes()).seconds(0).milliseconds(0);
    if (isNightShift) {
        slotEndMoment.add(1, 'day');
    }
    const slotEndOnDate = slotEndMoment.toDate();
    if (moment(openStart.time).isSameOrAfter(slotEndOnDate)) {
        return { fallback: AutoCloseFallback.END_OF_WORKDAY_ENTRY_AFTER_SLOT };
    }
    return { fallback: null, slotEndOnDate };
};

const getAutoCloseFixedHoursDate = (openStart: TimeTrackingEntryPrototype, policy: TimeTrackingPolicy | undefined): AutoCloseFixedHoursData => {
    const autoCloseLimitHours = policy?.autoCloseLimitHours ?? DEFAULT_AUTO_CLOSE_LIMIT_HOURS;
    const autoCloseFixedHoursValue = policy?.autoCloseFixedHoursValue ?? DEFAULT_AUTO_CLOSE_FIXED_HOURS_VALUE;
    const autoCloseFixedMinutesValue = policy?.autoCloseFixedMinutesValue ?? DEFAULT_AUTO_CLOSE_FIXED_MINUTES_VALUE;
    return {
        autoCloseEntryTime: moment(openStart.time).add(autoCloseFixedHoursValue, 'hours').add(autoCloseFixedMinutesValue, 'minutes').toDate(),
        autoCloseTime: moment(openStart.time).add(autoCloseLimitHours, 'hours').toDate(),
    };
};

const getAutoCloseScheduledHoursDate = (
    openStart: TimeTrackingEntryPrototype,
    policy: TimeTrackingPolicy | undefined,
    context: AutoCloseDataContext,
): AutoCloseResult => {
    const limitDate = moment(openStart.time)
        .add(policy?.autoCloseLimitHours ?? DEFAULT_AUTO_CLOSE_LIMIT_HOURS, 'hours')
        .toDate();

    const { fallback, workedHours } = computeScheduledHoursFallback(context.estimatedHours, context.allEntries);
    if (fallback) {
        return { autoCloseEntryTime: limitDate, autoCloseTime: limitDate, autoCloseFallback: fallback };
    }

    const remainingHours = context.estimatedHours - workedHours;
    const autoCloseEntryTime = moment(openStart.time).add(remainingHours, 'hours').toDate();
    const autoCloseTime = moment.max(moment(autoCloseEntryTime), moment(limitDate)).toDate();
    return { autoCloseEntryTime, autoCloseTime, autoCloseFallback: null };
};

const getAutoCloseEndOfWorkdayDate = (
    openStart: TimeTrackingEntryPrototype,
    policy: TimeTrackingPolicy | undefined,
    context: AutoCloseDataContext,
): AutoCloseResult => {
    const limitDate = moment(openStart.time)
        .add(policy?.autoCloseLimitHours ?? DEFAULT_AUTO_CLOSE_LIMIT_HOURS, 'hours')
        .toDate();

    const { fallback, slotEndOnDate } = computeEndOfWorkdayFallback(context.timeSlots, context.dateString, context.timezone, openStart);
    if (fallback) {
        return { autoCloseEntryTime: limitDate, autoCloseTime: limitDate, autoCloseFallback: fallback };
    }

    const autoCloseTime = moment.max(moment(slotEndOnDate), moment(limitDate)).toDate();
    return { autoCloseEntryTime: slotEndOnDate, autoCloseTime, autoCloseFallback: null };
};

export const getAutoCloseData = (policy: TimeTrackingPolicy | undefined, context: AutoCloseDataContext): AutoCloseData => {
    const autoCloseMode = policy?.autoCloseMode ?? DEFAULT_AUTO_CLOSE_MODE;
    const openStart = getLatestOpenStart(context.allEntries);
    if (!openStart) return { autoCloseEntryTime: undefined, autoCloseTime: undefined, autoCloseFallback: null, autoCloseMode };
    switch (autoCloseMode) {
        case TimeTrackingPolicyAutoCloseMode.SCHEDULED_HOURS:
            return { ...getAutoCloseScheduledHoursDate(openStart, policy, context), autoCloseMode };
        case TimeTrackingPolicyAutoCloseMode.END_OF_WORKDAY:
            return { ...getAutoCloseEndOfWorkdayDate(openStart, policy, context), autoCloseMode };
        case TimeTrackingPolicyAutoCloseMode.FIXED_HOURS:
        default:
            return { ...getAutoCloseFixedHoursDate(openStart, policy), autoCloseMode, autoCloseFallback: null };
    }
};

export const deriveAutoCloseFallback = (summary: TimeTrackingDaySummary, timezone: string): AutoCloseFallback | null => {
    const { autoCloseMode, estimatedHours, timeSlots, timeTrackingEntries } = summary;
    switch (autoCloseMode) {
        case TimeTrackingPolicyAutoCloseMode.SCHEDULED_HOURS:
            return computeScheduledHoursFallback(estimatedHours, timeTrackingEntries).fallback;
        case TimeTrackingPolicyAutoCloseMode.END_OF_WORKDAY: {
            const openStart = getLatestOpenStart(timeTrackingEntries);
            if (!openStart) return timeSlots?.length ? null : AutoCloseFallback.END_OF_WORKDAY_NO_PLANNING;
            return computeEndOfWorkdayFallback(timeSlots ?? null, summary.dateString, timezone, openStart).fallback;
        }
        case TimeTrackingPolicyAutoCloseMode.FIXED_HOURS:
        default:
            return null;
    }
};
