import { SchedulesOverridesAdapterMongoose } from "./SchedulesOverrides.Adapter.Mongoose";
import {
    CreateSchedulesOverridesParams,
    UpdateSchedulesOverridesParams,
    DeleteSchedulesOverridesParams,
    FindSchedulesOverridesParams,
    ISchedulesOverrides,
    ISchedulesOverridesManager,
    AvailableSchedulesOverridesParams,
    DeleteSchedulesOverridesByCompanyParams,
    DeleteSchedulesOverridesByEmployeeParams,
} from "./SchedulesOverrides.Interface";
import { ScheduleItem } from "../Schedules/Schedules.Interface";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import EmployeesList from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { NotificationsManager } from "../Notifications/Notifications";
import dayjs from "dayjs";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

class SchedulesOverridesManager implements ISchedulesOverridesManager {
    schedulesOverrides: SchedulesOverridesAdapterMongoose;

    constructor() {
        this.schedulesOverrides = new SchedulesOverridesAdapterMongoose();
    }

    public async deleteSchedulesOverridesByEmployee(data: DeleteSchedulesOverridesByEmployeeParams): Promise<void> {
        await this.schedulesOverrides.schedulesOverridesList.deleteMany({ employeeId: data.employeeId });
    }

    public async deleteSchedulesOverridesByCompany(data: DeleteSchedulesOverridesByCompanyParams): Promise<void> {
        await this.schedulesOverrides.schedulesOverridesList.deleteMany({ companyId: data.companyId });
    }

    public async createSchedulesOverrides(data: CreateSchedulesOverridesParams): Promise<ISchedulesOverrides> {
        const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        const employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        const exactDate = dayjs(data.date).startOf("day").toDate();

        const ScheduleCheck = await this.schedulesOverrides.findOne({
            employeeId: data.employeeId,
            companyId: data.companyId,
            date: exactDate,
        });

        if (ScheduleCheck) {
            throw new Error("Ya existe una excepción de horario para este empleado en esta fecha");
        }

        const newOverride = await this.schedulesOverrides.create({
            ...data,
            date: exactDate,
        });

        const strHorarios = data.schedules.map((horario) => `De ${horario.from} a ${horario.to}`);
        const strFecha = dayjs(exactDate).format("DD/MM/YYYY");

        await NotificationsManager.sendSystemNotification({
                userId: String(employeeCheck.userId),
                subject: "Se han modificado los horarios para una fecha específica",
                message: `Se han registrado horarios excepcionales en la organización ${companyCheck.name} para el día ${strFecha}. Horarios: ${strHorarios.join(", ")}`,
                type: NotificationType.SCHEDULE_EXCEPTION,
                code: String(employeeCheck._id)
            });

        return newOverride;
    }

    public async updateSchedulesOverrides(data: UpdateSchedulesOverridesParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        const employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        const exactDate = dayjs(data.date).startOf("day").toDate();

        const scheduleUpd = await this.schedulesOverrides.findOne({
            employeeId: data.employeeId,
            companyId: data.companyId,
            date: exactDate,
        });

        if (!scheduleUpd) {
            await this.createSchedulesOverrides({ ...data, date: exactDate });
            return;
        }

        scheduleUpd.schedules = data.schedules;
        await scheduleUpd.save();

        const strHorarios = data.schedules.map((horario) => `De ${horario.from} a ${horario.to}`);
        const strFecha = dayjs(exactDate).format("DD/MM/YYYY");

        await NotificationsManager.sendSystemNotification({
                userId: String(employeeCheck.userId),
                subject: "Se han modificado los horarios para una fecha específica",
                message: `Se han actualizado horarios excepcionales en la organización ${companyCheck.name} para el día ${strFecha}. Horarios: ${strHorarios.join(", ")}`,
                type: NotificationType.SCHEDULE_EXCEPTION,
                code: String(employeeCheck._id)
            });
    }

    public async find(filters: FindSchedulesOverridesParams): Promise<ISchedulesOverrides[]> {
        return await this.schedulesOverrides.find(filters);
    }

    public async findOne(filters: FindSchedulesOverridesParams): Promise<ISchedulesOverrides | null> {
        return await this.schedulesOverrides.findOne(filters);
    }

    private async getScheduleByFrom(data: AvailableSchedulesOverridesParams): Promise<ScheduleItem | null> {
        const exactDate = dayjs(data.dateDay).startOf("day").toDate();
        const schedule = await this.schedulesOverrides.findOne({
            employeeId: data.employeeId,
            companyId: data.companyId,
            date: exactDate,
        });

        if (schedule) {
            for (const horario of schedule.schedules) {
                if (horario.disabled) continue;
                if (horario.from == data.from) return horario;
                if (horario.from < data.from && horario.to > data.from) return horario;
            }
        }
        return null;
    }

    private checkScheduleTo(schedule: ScheduleItem, to: string): boolean {
        const toSchedule = schedule.to.split(":");
        const toTime = parseInt(toSchedule[0]) + parseInt(toSchedule[1]) / 60;

        const dataTo = to.split(":");
        const dataToTime = parseInt(dataTo[0]) + parseInt(dataTo[1]) / 60;

        if (dataToTime <= toTime) return true;
        return false;
    }

    public async isAvailable(data: AvailableSchedulesOverridesParams): Promise<boolean> {
        const scheduleFrom = await this.getScheduleByFrom(data);

        if (scheduleFrom) {
            if (this.checkScheduleTo(scheduleFrom, data.to)) {
                return true;
            }

            const scheduleTo = await this.getScheduleByFrom({
                ...data,
                from: scheduleFrom.to,
            });

            if (!scheduleTo) return false;
            if (this.checkScheduleTo(scheduleTo, data.to)) return true;
        }

        return false;
    }

    public async delete(data: DeleteSchedulesOverridesParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        const deleteItem = await this.schedulesOverrides.findOne({ _id: data.id });
        if (!deleteItem) {
            throw new Error("Ha ocurrido un error al borrar el horario excepcional");
        }

        await this.schedulesOverrides.delete(String(deleteItem._id));
    }
}

const SchedulesOverridesList = new SchedulesOverridesManager();

export default SchedulesOverridesList;
