"use strict";
/**
 * Business timeout calculation utilities.
 * Ported from steedos-platform packages/core/src/holidays/business_timeout.ts
 * 
 * Uses @steedos/objectql (already a project dependency) to access holidays and
 * business_hours objects, instead of @steedos/core.
 */
const moment = require('moment');
const _ = require('lodash');
const { getObject } = require('@steedos/objectql');
const {
    BusinessHoursCheckedType,
    getBusinessHoursPerDay,
    computeIsBusinessDate,
    computeNextBusinessDate
} = require('./business_hours');

// Simple in-memory cache for holidays and business hours with TTL
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
const _holidaysCache = {};  // key: `${spaceId}-${year}`, value: { values: [...], timestamp }
const _businessHoursCache = {}; // key: spaceId, value: { records: [...], timestamp }

/**
 * 从数据库中取出指定工作区内的节假日数据，支持缓存
 * @param {string} spaceId
 * @param {Array<number>} years
 */
async function getHolidays(spaceId, years) {
    const holidaysFields = ["name", "type", "date", "adjusted_to", "space"];
    for (const year of years) {
        const cacheKey = `${spaceId}-${year}`;
        const cached = _holidaysCache[cacheKey];
        if (!cached || (Date.now() - cached.timestamp > CACHE_TTL_MS)) {
            const yearStartDate = moment.utc(`${year}-01-01T00:00:00Z`).toDate();
            const yearEndDate = moment.utc(`${year}-12-31T23:59:59Z`).toDate();
            const objectHolidays = getObject("holidays");
            const holidaysRecords = await objectHolidays.find({
                filters: [["space", "=", spaceId], ["date", "between", [yearStartDate, yearEndDate]]],
                fields: holidaysFields
            });
            _holidaysCache[cacheKey] = {
                space: spaceId,
                year: year,
                values: (holidaysRecords && holidaysRecords.length) ? holidaysRecords : [],
                timestamp: Date.now()
            };
        }
    }
    const result = [];
    for (const year of years) {
        const cacheKey = `${spaceId}-${year}`;
        if (_holidaysCache[cacheKey] && _holidaysCache[cacheKey].values) {
            result.push(..._holidaysCache[cacheKey].values);
        }
    }
    return result;
}

/**
 * 从数据库中取出指定工作区内的默认工作时间数据，支持缓存
 * @param {string} spaceId
 */
async function getDefaultBusinessHours(spaceId) {
    const cached = _businessHoursCache[spaceId];
    if (cached && (Date.now() - cached.timestamp <= CACHE_TTL_MS)) {
        return cached.records;
    }
    const objectBusinessHours = getObject("business_hours");
    const records = await objectBusinessHours.find({
        filters: [["space", "=", spaceId], ["is_default", "=", true]],
        fields: ["name", "start", "end", "lunch_start", "lunch_end", "utc_offset", "working_days", "space"]
    });
    if (records && records.length) {
        _businessHoursCache[spaceId] = { records, timestamp: Date.now() };
    }
    return records || [];
}

/**
 * 计算以某个时间点开始，超时几个小时后，超时时间点是什么时候 (纯计算，不查数据库)
 * @param {Date} start
 * @param {number} timeoutHours
 * @param {Array} holidays
 * @param {object} businessHours
 * @param {number} digitsForHours
 * @returns {Date}
 */
function computeTimeoutDateWithoutHolidays(start, timeoutHours, holidays, businessHours, digitsForHours = 2) {
    if (timeoutHours <= 0) {
        return start;
    }
    const utcOffset = businessHours.utc_offset;
    const businessHoursPerDay = getBusinessHoursPerDay(businessHours, digitsForHours);
    const startBhct = computeIsBusinessDate(start, holidays, businessHours, digitsForHours);
    let offsetMinutes = 0;
    let startMoment = moment.utc(start);
    let startClosingMoment = moment.utc(start);
    if (startBhct <= 0) {
        if (startBhct === BusinessHoursCheckedType.offLunch) {
            startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
            startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
            start = startMoment.toDate();
        } else if (startBhct === BusinessHoursCheckedType.offAm) {
            startMoment.hours(businessHoursPerDay.startValue.hours - utcOffset);
            startMoment.minutes(businessHoursPerDay.startValue.minutes);
            start = startMoment.toDate();
            startClosingMoment = moment.utc(start);
            startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
            startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
            offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
            startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
            startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
            start = startMoment.toDate();
        } else {
            const nextBusinessDate = computeNextBusinessDate(start, holidays, businessHours, digitsForHours);
            start = nextBusinessDate.start;
            startMoment = moment.utc(start);
            startClosingMoment = moment.utc(start);
            startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
            startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
            offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
            startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
            startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
            start = startMoment.toDate();
        }
    } else {
        if (startBhct === BusinessHoursCheckedType.onAm) {
            startClosingMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
            startClosingMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
            offsetMinutes = startClosingMoment.diff(startMoment, 'minute');
            startMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
            startMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
            start = startMoment.toDate();
        } else {
            startMoment = moment.utc(start);
        }
    }
    startClosingMoment.hours(businessHoursPerDay.endValue.hours - utcOffset);
    startClosingMoment.minutes(businessHoursPerDay.endValue.minutes);
    offsetMinutes += startClosingMoment.diff(startMoment, 'minute');
    const timeoutMinutes = timeoutHours * 60;
    if (timeoutMinutes <= offsetMinutes) {
        let subMinutes = offsetMinutes - timeoutMinutes;
        if (subMinutes > 0) {
            if (subMinutes >= businessHoursPerDay.computedPmMinutes) {
                subMinutes += businessHoursPerDay.computedLunchMinutes;
            }
            startClosingMoment.subtract(subMinutes, 'm');
        }
        return startClosingMoment.toDate();
    } else {
        let nextMoment = startClosingMoment;
        for (let i = 0; offsetMinutes < timeoutMinutes; i++) {
            const nextBusinessDate = computeNextBusinessDate(nextMoment.toDate(), holidays, businessHours, digitsForHours);
            if (nextBusinessDate) {
                nextMoment = moment.utc(nextBusinessDate.end);
                offsetMinutes += businessHoursPerDay.computedMinutes;
            } else {
                throw new Error("computeTimeoutDateWithoutHolidays:Maximum number of calls, The number of days in holidays may exceed 365.");
            }
        }
        if (offsetMinutes > timeoutMinutes) {
            let subMinutes = offsetMinutes - timeoutMinutes;
            if (subMinutes >= businessHoursPerDay.computedPmMinutes) {
                subMinutes += businessHoursPerDay.computedLunchMinutes;
            }
            nextMoment.subtract(subMinutes, 'm');
            return nextMoment.toDate();
        }
        return nextMoment.toDate();
    }
}

/**
 * 计算在某个工作区下的节假日、工作时间数据基础上，以某个时间点开始，超时几个小时后，超时时间点是什么时候
 * @param {Date} start
 * @param {number} timeoutHours
 * @param {string} spaceId
 * @param {number} digitsForHours
 * @returns {Promise<Date>}
 */
async function getTimeoutDateWithoutHolidays(start, timeoutHours, spaceId, digitsForHours = 2) {
    let enableHolidays = false;
    try {
        const { getSteedosConfig } = require('@steedos/objectql');
        const config = getSteedosConfig();
        enableHolidays = !!config.enable_holidays;
    } catch (e) {
        // If config is not available, treat as holidays not enabled
    }
    if (!enableHolidays) {
        return moment(start).add(timeoutHours, 'h').toDate();
    }
    const defaultBusinessHoursRecords = await getDefaultBusinessHours(spaceId);
    const defaultBusinessHoursRecord = defaultBusinessHoursRecords && defaultBusinessHoursRecords[0];
    if (!defaultBusinessHoursRecord) {
        // 未配置工作时间，直接按未开启enable_holidays处理
        return moment(start).add(timeoutHours, 'h').toDate();
    }
    const startUTCYear = start.getUTCFullYear();
    const holidaysRecords = await getHolidays(spaceId, [startUTCYear, startUTCYear + 1]);
    return computeTimeoutDateWithoutHolidays(start, timeoutHours, holidaysRecords, defaultBusinessHoursRecord, digitsForHours);
}

module.exports = {
    getHolidays,
    getDefaultBusinessHours,
    getTimeoutDateWithoutHolidays,
    computeTimeoutDateWithoutHolidays
};
