import { ENV_OPTIONAL_NUMBER_KEYS_MAPPING, SEQUELIZE_INSTANCE_TOKEN } from '@humand-packages/common';
import { Includeable } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { Inject, Service } from 'typedi';
import { BaseRepositoryAdapter } from '../../../../../../dbAdapters/baseRepositoryAdapter';
import { GridFilterRequest } from '../../../../business/interfaces/shiftManagement/gridFiltersInterface';
import { ShiftAssignedShift, ShiftAssignedShiftForCreation } from '../../../../business/models/shiftManagement/shiftAssignedShift';
import { ShiftAssignedShiftSource } from '../../../../business/types/shiftManagement/source';
import { ShiftDAO } from '../../../dataAccessObjects/shiftManagement/shift';
import { ShiftAssignedDayDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedDay';
import { ShiftAssignedShiftDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedShifts';
import { ShiftAssignedTimeSlotDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedTimeSlot';
import { mapShiftAssignedShiftDAOToModel } from '../../../mappers/shiftManagement/shiftAssignedDayMapper';
import ShiftAssignmentRepositoryPort from '../../../ports/shiftManagement/shiftAssignmentRepositoryPort';
import { buildShiftAssignedDayWhere } from './queryBuilders/grid';
import { getShiftAssignedShiftInclude } from './queryBuilders/shiftRelation';

@Service()
export default class ShiftAssignmentRepositoryAdapter extends BaseRepositoryAdapter implements ShiftAssignmentRepositoryPort {
    constructor(
        @Inject(ENV_OPTIONAL_NUMBER_KEYS_MAPPING.BULK_CREATE_CHUNK_SIZE) bulkCreateChunkSize: number,
        @Inject(SEQUELIZE_INSTANCE_TOKEN) sequelize: Sequelize,
    ) {
        super(bulkCreateChunkSize, sequelize);
    }

    public bulkCreateShiftAssignedShifts = async (shiftAssignedShifts: ShiftAssignedShiftForCreation[]): Promise<ShiftAssignedShift[]> => {
        return this.getTransaction(async () => {
            const shiftAssignedShiftsDAO = await ShiftAssignedShiftDAO.bulkCreate(shiftAssignedShifts);
            return shiftAssignedShiftsDAO.map(mapShiftAssignedShiftDAOToModel);
        });
    };

    public deleteAssignedShift = async (shiftAssignedShiftId: number, instanceId: number): Promise<number> => {
        return await this.getTransaction(async () => {
            let rowsDeleted = 0;
            const shiftAssignedShift = await ShiftAssignedShiftDAO.findByPk(shiftAssignedShiftId, {
                include: [{ model: ShiftDAO }],
            });
            if (shiftAssignedShift) {
                if (shiftAssignedShift.source === ShiftAssignedShiftSource.INTEGRATION) {
                    await ShiftAssignedTimeSlotDAO.destroy({
                        where: { assignedShiftId: shiftAssignedShiftId },
                    });
                }
                rowsDeleted = await ShiftAssignedShiftDAO.destroy({ where: { id: shiftAssignedShiftId, instanceId } });
            }
            return rowsDeleted;
        });
    };

    public bulkDeleteShiftAssignedShifts = async (instanceId: number, filters: GridFilterRequest): Promise<void> => {
        const { assignedDaysWhere } = buildShiftAssignedDayWhere(instanceId, filters);
        if (!assignedDaysWhere) return;

        const include: Includeable[] = getShiftAssignedShiftInclude({
            assignedShiftsRequired: true,
            options: { excludeTimeSlots: true, shiftAssignedShiftsAttributes: ['id'], shiftAttributes: [] },
        });

        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            attributes: ['id'],
            include,
            where: assignedDaysWhere,
        });

        if (shiftAssignedDaysDAO.length === 0) return;

        const shiftAssignedShiftsDAO = shiftAssignedDaysDAO.flatMap((day) => day.shiftAssociations);
        const shiftAssignedShiftIds: number[] = shiftAssignedShiftsDAO.map((shiftAssignedShiftDAO) => shiftAssignedShiftDAO.id);
        await ShiftAssignedTimeSlotDAO.destroy({
            where: {
                assignedShiftId: shiftAssignedShiftIds,
            },
        });
        await ShiftAssignedShiftDAO.destroy({
            where: {
                id: shiftAssignedShiftIds,
                instanceId,
            },
        });
    };

    /**
     * Updates the assigned day (ShiftAssignedDay.id) for a set of shiftAssignedShifts.
     * @param instanceId - The instance ID.
     * @param updates - The updates to make.
     * @returns A promise that resolves when the updates are complete.
     */
    public bulkUpdateShiftAssignedShiftsAssignedDay = async (
        instanceId: number,
        updates: Array<{ shiftAssignedShiftId: number; newAssignedDayId: number }>,
    ): Promise<void> => {
        if (updates.length === 0) return;
        await this.getTransaction(async (transaction) => {
            for (const { shiftAssignedShiftId, newAssignedDayId } of updates) {
                await ShiftAssignedShiftDAO.update(
                    { assignedDayId: newAssignedDayId },
                    { transaction, where: { id: shiftAssignedShiftId, instanceId } },
                );
            }
        });
    };
}
