import { ENV_OPTIONAL_NUMBER_KEYS_MAPPING, SEQUELIZE_INSTANCE_TOKEN } from '@humand-packages/common';
import { Op } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { Inject, Service } from 'typedi';
import { BaseRepositoryAdapter } from '../../../../../../dbAdapters/baseRepositoryAdapter';
import { LimitOffsetPagination } from '../../../../../../types/listOptions';
import { Shift, ShiftForCreation } from '../../../../business/models/shiftManagement/shift';
import { ShiftsListFiltersVC } from '../../../../presentation/validationClasses/shiftManagement/shiftsListFiltersVC';
import { ShiftDAO } from '../../../dataAccessObjects/shiftManagement/shift';
import { ShiftTimeSlotDAO } from '../../../dataAccessObjects/shiftManagement/shiftTimeSlot';
import { mapShiftDAOToModel } from '../../../mappers/shiftManagement/shiftAssignedDayMapper';
import ShiftRepositoryPort from '../../../ports/shiftManagement/shiftRepositoryPort';

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

    public async getShifts(
        instanceId: number,
        filters: ShiftsListFiltersVC,
        pagination: LimitOffsetPagination,
    ): Promise<{ count: number; rows: Shift[] }> {
        const { search } = filters;
        const { count, rows } = await ShiftDAO.findAndCountAll({
            include: [
                {
                    as: 'timeSlots',
                    model: ShiftTimeSlotDAO,
                },
            ],
            limit: pagination.limit,
            offset: pagination.offset,
            order: [['createdAt', 'DESC']],
            where: {
                instanceId,
                isTemplate: true,
                lastVersion: true,
                ...(search ? { name: { [Op.iLike]: `%${search}%` } } : {}),
            },
        });

        return {
            count,
            rows: rows.map((row) => mapShiftDAOToModel(row)),
        };
    }

    public async createShift(instanceId: number, shift: ShiftForCreation): Promise<Shift> {
        return this.getTransaction(async () => {
            const shiftDAO = await ShiftDAO.create(shift);
            const shiftTimeSlotDAOs = await ShiftTimeSlotDAO.bulkCreate(
                shift.timeSlots.map((timeSlot) => ({ ...timeSlot, instanceId, shiftId: shiftDAO.id })),
                { returning: true },
            );
            return mapShiftDAOToModel(shiftDAO, shiftTimeSlotDAOs);
        });
    }

    public async getById(id: number): Promise<Shift | null> {
        const shiftDAO = await ShiftDAO.findByPk(id, {
            include: [{ model: ShiftTimeSlotDAO }],
        });
        return shiftDAO ? mapShiftDAOToModel(shiftDAO) : null;
    }

    public async updateShiftTemplate(instanceId: number, newShift: ShiftForCreation, existingShift: Shift): Promise<Shift> {
        return this.getTransaction(async () => {
            await ShiftDAO.update({ lastVersion: false }, { where: { id: existingShift.id, instanceId, isTemplate: true } });

            const newShiftDAO = await ShiftDAO.create(newShift);

            const shiftTimeSlotDAOs = await ShiftTimeSlotDAO.bulkCreate(
                newShift.timeSlots.map((timeSlot) => ({ ...timeSlot, instanceId, shiftId: newShiftDAO.id })),
                { returning: true },
            );

            return mapShiftDAOToModel(newShiftDAO, shiftTimeSlotDAOs);
        });
    }

    public async bumpShiftVersion(instanceId: number, newShift: ShiftForCreation, existingShift: Shift): Promise<Shift> {
        return this.getTransaction(async () => {
            await ShiftDAO.update({ lastVersion: false }, { where: { id: existingShift.id, instanceId } });

            const newShiftDAO = await ShiftDAO.create(newShift);

            const shiftTimeSlotDAOs = await ShiftTimeSlotDAO.bulkCreate(
                newShift.timeSlots.map((timeSlot) => ({ ...timeSlot, instanceId, shiftId: newShiftDAO.id })),
                { returning: true },
            );

            return mapShiftDAOToModel(newShiftDAO, shiftTimeSlotDAOs);
        });
    }

    public async removeFromTemplates(instanceId: number, shiftTemplateId: number): Promise<void> {
        await ShiftDAO.update({ isTemplate: false }, { where: { id: shiftTemplateId, instanceId } });
    }

    public async getShiftsByCodes(instanceId: number, codes: string[]): Promise<Shift[]> {
        if (codes.length === 0) return [];
        const daos = await ShiftDAO.findAll({
            include: [{ as: 'timeSlots', model: ShiftTimeSlotDAO }],
            where: {
                code: { [Op.in]: codes },
                instanceId,
                lastVersion: true,
            },
        });
        return daos.map((dao) => mapShiftDAOToModel(dao, dao.timeSlots));
    }

    public async getTemplatesByIds(instanceId: number, ids: number[]): Promise<Shift[]> {
        if (ids.length === 0) return [];
        const daos = await ShiftDAO.findAll({
            include: [{ as: 'timeSlots', model: ShiftTimeSlotDAO }],
            where: {
                id: { [Op.in]: ids },
                instanceId,
                isTemplate: true,
            },
        });
        return daos.map((dao) => mapShiftDAOToModel(dao, dao.timeSlots));
    }

    public async deleteByCodes(instanceId: number, codes: string[]): Promise<void> {
        if (codes.length === 0) return;
        await ShiftDAO.destroy({ where: { code: { [Op.in]: codes }, instanceId } });
    }
}
