"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const common_1 = require("@nestjs/common");
const schedule_1 = require("@nestjs/schedule");
const date_fns_tz_1 = require("date-fns-tz");
const addMinutes_1 = require("date-fns/addMinutes");
const isBefore_1 = require("date-fns/isBefore");
const typeorm_1 = require("typeorm");
const config_1 = require("./config");
const entities_1 = require("./config/database/entities");
const timezone_util_1 = require("./common/timezone/timezone.util");
const notifications_service_1 = require("./modules/notifications/notifications.service");
const utils_1 = require("./utils");
let AppService = class AppService {
    constructor(uploadService, db, mailService, notificationService) {
        this.uploadService = uploadService;
        this.db = db;
        this.mailService = mailService;
        this.notificationService = notificationService;
    }
    async uploadFiles(files, type) {
        try {
            config_1.logger.info('get files ' + type);
            config_1.logger.info('get files ' + files);
            if (!files || !files.length) {
                throw new config_1.ErrorResponse('Files not found', 400);
            }
            const { host } = (0, config_1.envConfig)().server || {};
            const paths = await this.uploadService.saveImages(files, 'temp');
            if (type === 'single') {
                return {
                    ...paths[0],
                    file_name: `${host}/${paths[0].file_name}`,
                };
            }
            return paths?.map((file) => ({
                ...file,
                file_name: `${host}/${file.file_name}`,
            }));
        }
        catch (error) {
            config_1.logger.error(`file upload error: ${error}`);
            if (error instanceof config_1.ErrorResponse) {
                throw error;
            }
            throw new config_1.ErrorResponse(error, 400);
        }
    }
    async handleReminderCorn() {
        try {
            config_1.logger.log('Checkout reminder cron started');
            const now = new Date();
            const owners = await this.db.owner.findMany({
                where: { status: entities_1.StatusEnum.active },
                include: { workingSchedule: true },
            });
            for (const owner of owners) {
                try {
                    const schedule = owner.workingSchedule;
                    if (!schedule)
                        continue;
                    const timeZone = (0, timezone_util_1.normalizeTimezone)(schedule.timezone);
                    const todayStr = (0, date_fns_tz_1.formatInTimeZone)(now, timeZone, 'yyyy-MM-dd');
                    const { startUtc: todayStart, endUtc: todayEnd } = (0, utils_1.getUtcStartEndOfDate)(todayStr, timeZone);
                    const officeEnd = new Date(schedule.office_end_time);
                    const officeHour = Number((0, date_fns_tz_1.formatInTimeZone)(officeEnd, timeZone, 'HH'));
                    const officeMinute = Number((0, date_fns_tz_1.formatInTimeZone)(officeEnd, timeZone, 'mm'));
                    const officeSecond = Number((0, date_fns_tz_1.formatInTimeZone)(officeEnd, timeZone, 'ss'));
                    const officeEndToday = (0, date_fns_tz_1.fromZonedTime)(`${todayStr} ${String(officeHour).padStart(2, '0')}:${String(officeMinute).padStart(2, '0')}:${String(officeSecond).padStart(2, '0')}`, timeZone);
                    const flexibleReminderTime = (0, date_fns_tz_1.fromZonedTime)(`${todayStr} 23:30:00`, timeZone);
                    const attendances = await this.db.attendances.findMany({
                        where: {
                            owner_id: owner.id,
                            date: (0, typeorm_1.Between)(todayStart, todayEnd),
                            check_in: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()),
                            check_out: (0, typeorm_1.IsNull)(),
                            checkout_reminder_sent: false,
                        },
                        include: { member: true },
                    });
                    if (!attendances.length)
                        continue;
                    for (const attendance of attendances) {
                        try {
                            const member = attendance.member;
                            if (!member)
                                continue;
                            let reminderTime;
                            switch (attendance.type) {
                                case entities_1.AttendanceTypeEnum.flexible:
                                    reminderTime = flexibleReminderTime;
                                    break;
                                case entities_1.AttendanceTypeEnum.in_office:
                                default:
                                    reminderTime = (0, addMinutes_1.addMinutes)(officeEndToday, -30);
                                    break;
                            }
                            if ((0, isBefore_1.isBefore)(now, reminderTime))
                                continue;
                            await this.mailService.sendMail('attendance-reminder-mail', member?.email, {
                                name: member?.first_name
                                    ? `${member?.first_name} ${member?.last_name}`
                                    : member.nickname,
                                checkInTime: attendance.check_in,
                                workedHours: attendance.work_hours,
                                dashboard: 'https://team-tracker.rinov8.studio/admin/dashboard',
                            }, 'Checkout Reminder');
                            await this.db.attendances.update({
                                where: { id: attendance.id },
                                data: {
                                    checkout_reminder_sent: true,
                                    checkout_reminder_sent_at: new Date(),
                                },
                            });
                            await this.notificationService.sendNotification({
                                title: 'Checkout Reminder',
                                message: `Hi ${member.first_name}, this is a reminder to check out for today. You checked in at ${(0, date_fns_tz_1.formatInTimeZone)(attendance.check_in, timeZone, 'hh:mm a')} and have currently completed ${attendance.work_hours} working hours. Please complete your checkout if your workday has ended.`,
                                member_id: member.id,
                                type: entities_1.NotificationTypeEnum.attendance,
                                owner_id: owner.id,
                                include_owner: false,
                                start_time: attendance?.check_in,
                                duration: 0,
                            });
                            config_1.logger.log(`Reminder sent → ${member.email} (${attendance.type})`);
                        }
                        catch (err) {
                            config_1.logger.error(`Failed attendance reminder: ${attendance.id}`, err?.stack);
                        }
                    }
                }
                catch (err) {
                    config_1.logger.error(`Owner processing failed: ${owner.id}`, err?.stack);
                }
            }
            config_1.logger.log('Checkout reminder cron completed');
        }
        catch (err) {
            config_1.logger.error('Checkout reminder cron crashed', err?.stack);
        }
    }
    async generateDailyAttendance() {
        config_1.logger.log('Attendance cron started');
        console.log('Attendance cron started at', new Date().toISOString());
        try {
            const now = new Date();
            const [members, owners, workDaySchedules, recurringHolidays, nonRecurringHolidays, approvedLeaves, existingAttendances, openPreviousAttendances,] = await Promise.all([
                this.db.members.findMany({ where: { status: 'active' } }),
                this.db.owner.findMany({
                    include: { workingSchedule: true },
                }),
                this.db.work_day_schedules.findMany({}),
                this.db.holidays.findMany({
                    where: { is_recurring: true },
                    include: { departments: true },
                }),
                this.db.holidays.findMany({
                    where: { is_recurring: false },
                    include: { departments: true },
                }),
                this.db.leaves.findMany({
                    where: {
                        status: 'approved',
                    },
                }),
                this.db.attendances.findMany({
                    select: { member_id: true, date: true, owner_id: true },
                }),
                this.db.attendances.findMany({
                    where: {
                        check_in: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()),
                        check_out: (0, typeorm_1.IsNull)(),
                        status: (0, typeorm_1.Not)(entities_1.AttendanceStatusEnum.absent),
                    },
                    include: { member: true },
                }),
            ]);
            const ownerScheduleMap = new Map(owners.map((o) => [o.id, o.workingSchedule]));
            const ownerHolidayMap = new Map();
            for (const owner of owners) {
                const timeZone = (0, timezone_util_1.normalizeTimezone)(owner.workingSchedule?.timezone);
                const todayStr = (0, date_fns_tz_1.formatInTimeZone)(now, timeZone, 'yyyy-MM-dd');
                const { startUtc: ownerTodayStart } = (0, utils_1.getUtcStartEndOfDate)(todayStr, timeZone);
                const ownerRecurring = recurringHolidays.filter((h) => h.owner_id === owner.id);
                const ownerNonRecurring = nonRecurringHolidays.filter((h) => {
                    if (h.owner_id !== owner.id)
                        return false;
                    return (new Date(h.start_date) <= ownerTodayStart &&
                        new Date(h.end_date) >= ownerTodayStart);
                });
                const holidays = (0, utils_1.getCurrentMonthHolidays)(ownerRecurring, ownerNonRecurring, ownerTodayStart, ownerTodayStart, timeZone);
                ownerHolidayMap.set(owner.id, holidays);
            }
            for (const att of openPreviousAttendances) {
                try {
                    if (!att.member)
                        continue;
                    const schedule = ownerScheduleMap.get(att.member.owner_id);
                    const timeZone = (0, timezone_util_1.normalizeTimezone)(schedule?.timezone);
                    const todayStr = (0, date_fns_tz_1.formatInTimeZone)(now, timeZone, 'yyyy-MM-dd');
                    const { startUtc: memberTodayStart } = (0, utils_1.getUtcStartEndOfDate)(todayStr, timeZone);
                    const attendanceDate = new Date(att.date);
                    if (attendanceDate.getTime() >= memberTodayStart.getTime()) {
                        continue;
                    }
                    await this.db.attendances.update({
                        where: { id: att.id },
                        data: {
                            status: entities_1.AttendanceStatusEnum.absent,
                        },
                    });
                    config_1.logger.log(`Updated previous day attendance for member ${att.member_id}`);
                }
                catch (err) {
                    config_1.logger.error(`Failed to update previous attendance ${att.id}`, err?.stack);
                }
            }
            for (const member of members) {
                try {
                    const ownerId = member.owner_id;
                    const schedule = ownerScheduleMap.get(ownerId);
                    const timeZone = (0, timezone_util_1.normalizeTimezone)(schedule?.timezone);
                    const todayStr = (0, date_fns_tz_1.formatInTimeZone)(now, timeZone, 'yyyy-MM-dd');
                    const { startUtc: memberTodayStart } = (0, utils_1.getUtcStartEndOfDate)(todayStr, timeZone);
                    const alreadyExists = existingAttendances.some((attendance) => attendance.member_id === member.id &&
                        new Date(attendance.date).getTime() === memberTodayStart.getTime());
                    if (alreadyExists) {
                        continue;
                    }
                    const existingCount = await this.db.attendances.count({
                        where: {
                            member_id: member.id,
                            date: memberTodayStart,
                        },
                    });
                    if (existingCount > 0) {
                        config_1.logger.warn(`Duplicate prevented for member ${member.id}`);
                        continue;
                    }
                    const holidays = ownerHolidayMap.get(ownerId) ?? [];
                    let status = entities_1.AttendanceStatusEnum.absent;
                    const isHoliday = holidays.some((holiday) => {
                        if (!holiday.departments?.length)
                            return true;
                        return holiday.departments.some((d) => d.id === member.department_id);
                    });
                    if (isHoliday) {
                        status = entities_1.AttendanceStatusEnum.holiday;
                    }
                    else {
                        const dayOfWeek = Number((0, date_fns_tz_1.formatInTimeZone)(now, timeZone, 'i')) % 7;
                        const todayDaySchedule = workDaySchedules.find((s) => s.owner_id === ownerId && s.day_of_week === dayOfWeek);
                        const isWeekend = todayDaySchedule
                            ? !todayDaySchedule.is_active
                            : false;
                        if (isWeekend) {
                            status = entities_1.AttendanceStatusEnum.weekend;
                        }
                        else {
                            const isOnLeave = approvedLeaves.some((l) => l.member_id === member.id &&
                                new Date(l.start_date) <= memberTodayStart &&
                                new Date(l.end_date) >= memberTodayStart);
                            if (isOnLeave) {
                                status = entities_1.AttendanceStatusEnum.on_leave;
                            }
                        }
                    }
                    await this.db.attendances.create({
                        data: {
                            member_id: member.id,
                            owner_id: ownerId,
                            date: todayStr,
                            status,
                            type: entities_1.AttendanceTypeEnum.in_office,
                        },
                    });
                    config_1.logger.log(`Attendance created for member ${member.id} → ${status}`);
                }
                catch (err) {
                    if (err?.code === 'ER_DUP_ENTRY' || err?.errno === 1062) {
                        config_1.logger.warn(`Duplicate prevented for member ${member.id}`);
                    }
                    else {
                        config_1.logger.error(`Failed to create attendance for member ${member.id}`, err?.stack);
                    }
                }
            }
            config_1.logger.log('Attendance cron completed successfully');
        }
        catch (err) {
            config_1.logger.error('Attendance cron crashed', err?.stack);
        }
    }
};
exports.AppService = AppService;
__decorate([
    (0, schedule_1.Cron)('0 */10 * * * *'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], AppService.prototype, "handleReminderCorn", null);
__decorate([
    (0, schedule_1.Cron)('0 * * * *'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], AppService.prototype, "generateDailyAttendance", null);
exports.AppService = AppService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [config_1.UploadService,
        config_1.DatabaseService,
        config_1.MailService,
        notifications_service_1.NotificationsService])
], AppService);
//# sourceMappingURL=app.service.js.map