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 { GridFilterRequest } from '../../../../business/interfaces/shiftManagement/gridFiltersInterface';
import { ShiftAssignedDayForCreation, ShiftAssignedDay } from '../../../../business/models/shiftManagement/shiftAssignedDay';
import { ShiftAssignedShift, ShiftAssignedShiftForCreation } from '../../../../business/models/shiftManagement/shiftAssignedShift';
import { ShiftAssignedTimeSlot, ShiftAssignedTimeSlotForCreation } from '../../../../business/models/shiftManagement/shiftAssignedTimeSlot';
import { ShiftAssignedDayStatus } from '../../../../business/types/shiftAssignedDayStatus';
import { ShiftAssignedShiftStatus } from '../../../../business/types/shiftManagement/assignedShiftStatus';
import { ShiftAssignedDayDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedDay';
import { ShiftAssignedShiftDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedShifts';
import { ShiftAssignedTimeSlotDAO } from '../../../dataAccessObjects/shiftManagement/shiftAssignedTimeSlot';
import {
    mapShiftAssignedDayDAOToModel,
    mapShiftAssignedShiftDAOToModel,
    mapShiftAssignedTimeSlotDAOToModel,
} from '../../../mappers/shiftManagement/shiftAssignedDayMapper';
import ShiftAssignedDayRepositoryPort from '../../../ports/shiftManagement/shiftAssignedDayRepositoryPort';
import { buildShiftAssignedDayWhere } from './queryBuilders/grid';
import { getShiftAssignedShiftInclude } from './queryBuilders/shiftRelation';

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

    public getShiftAssignedDaysByUser = async (userId: number, instanceId: number, fromDate: string, toDate: string): Promise<ShiftAssignedDay[]> => {
        const include = getShiftAssignedShiftInclude({
            assignedShiftsRequired: true,
            assignedShiftsWhere: {
                status: ShiftAssignedShiftStatus.PUBLISHED,
            },
        });

        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            include,
            where: {
                dateString: {
                    [Op.between]: [fromDate, toDate],
                },
                instanceId,
                userId,
            },
        });

        return shiftAssignedDaysDAO.map(mapShiftAssignedDayDAOToModel);
    };

    public getShiftAssignedDayById = async (id: number): Promise<ShiftAssignedDay | null> => {
        const include = getShiftAssignedShiftInclude({
            assignedShiftsRequired: true,
            assignedShiftsWhere: {
                status: ShiftAssignedShiftStatus.PUBLISHED,
            },
        });
        const shiftAssignedDayDAO = await ShiftAssignedDayDAO.findByPk(id, { include });
        return shiftAssignedDayDAO ? mapShiftAssignedDayDAOToModel(shiftAssignedDayDAO) : null;
    };

    public getShiftAssignedDaysByIds = async (ids: number[]): Promise<ShiftAssignedDay[]> => {
        if (ids.length === 0) {
            return [];
        }
        const include = getShiftAssignedShiftInclude();
        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            include,
            where: { id: ids },
        });
        return shiftAssignedDaysDAO.map(mapShiftAssignedDayDAOToModel);
    };

    public getShiftAssignedDaysByGridFilters = async (instanceId: number, filters: GridFilterRequest): Promise<ShiftAssignedDay[]> => {
        const { assignedDaysWhere } = buildShiftAssignedDayWhere(instanceId, filters);
        if (!assignedDaysWhere) return [];

        const include = getShiftAssignedShiftInclude();

        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            include,
            where: assignedDaysWhere,
        });
        return shiftAssignedDaysDAO.map(mapShiftAssignedDayDAOToModel);
    };

    public getShiftAssignedDaysByShiftName = async (
        instanceId: number,
        shiftName: string,
        startDate: string,
        endDate: string,
    ): Promise<ShiftAssignedDay[]> => {
        const include = getShiftAssignedShiftInclude({
            assignedShiftsRequired: true,
            shiftRequired: true,
            shiftWhere: { instanceId, name: { [Op.iLike]: `%${shiftName}%` } },
        });

        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            include,
            where: {
                dateString: {
                    [Op.between]: [startDate, endDate],
                },
                instanceId,
                status: ShiftAssignedDayStatus.PUBLISHED,
            },
        });

        return shiftAssignedDaysDAO.map(mapShiftAssignedDayDAOToModel);
    };

    public getUserIdsWithRotationInRange = async (
        instanceId: number,
        rotationIds: string[],
        startDate: string,
        endDate: string,
    ): Promise<number[]> => {
        if (rotationIds.length === 0) {
            return [];
        }

        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.findAll({
            attributes: ['userId'],
            include: [
                {
                    attributes: [],
                    model: ShiftAssignedShiftDAO,
                    required: true,
                    where: { shiftRotationId: rotationIds },
                },
            ],
            where: {
                dateString: {
                    [Op.between]: [startDate, endDate],
                },
                instanceId,
                status: ShiftAssignedDayStatus.PUBLISHED,
            },
        });

        return [...new Set(shiftAssignedDaysDAO.map((shiftAssignedDay) => shiftAssignedDay.userId))];
    };

    public bulkCreateShiftAssignedDays = async (shiftAssignedDays: ShiftAssignedDayForCreation[]): Promise<ShiftAssignedDay[]> => {
        const shiftAssignedDaysDAO = await ShiftAssignedDayDAO.bulkCreate(shiftAssignedDays, {
            conflictAttributes: ['dateString', 'userId', 'status'],
            conflictWhere: {
                deletedAt: null,
            },
            returning: true,
            updateOnDuplicate: ['updatedAt'],
        });
        return shiftAssignedDaysDAO.map(mapShiftAssignedDayDAOToModel);
    };

    public getShiftAssignedShifts = async (instanceId: number, assignedShiftIds: number[]): Promise<ShiftAssignedShift[]> => {
        const shiftAssignedShiftsDAO = await ShiftAssignedShiftDAO.findAll({
            include: [
                {
                    model: ShiftAssignedDayDAO,
                    required: true,
                },
            ],
            where: {
                id: assignedShiftIds,
                instanceId,
            },
        });
        return shiftAssignedShiftsDAO.map(mapShiftAssignedShiftDAOToModel);
    };

    public bulkDeleteShiftAssignedDayAssociations = async (instanceId: number, assignedDayIds: number[]): Promise<void> => {
        const shiftAssignedShiftsDAO = await ShiftAssignedShiftDAO.findAll({
            attributes: ['id'],
            raw: true,
            where: {
                assignedDayId: assignedDayIds,
                instanceId,
            },
        });
        const shiftAssignedShiftIds = shiftAssignedShiftsDAO.map((shiftAssignedShiftDAO) => shiftAssignedShiftDAO.id);
        await ShiftAssignedShiftDAO.destroy({
            where: {
                id: shiftAssignedShiftIds,
            },
        });
        await ShiftAssignedTimeSlotDAO.destroy({
            where: {
                assignedShiftId: shiftAssignedShiftIds,
            },
        });
    };

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

    public bulkCreateShiftAssignedTimeSlots = async (
        shiftAssignedTimeSlots: ShiftAssignedTimeSlotForCreation[],
    ): Promise<ShiftAssignedTimeSlot[]> => {
        const shiftAssignedTimeSlotsDAO = await ShiftAssignedTimeSlotDAO.bulkCreate(shiftAssignedTimeSlots);
        return shiftAssignedTimeSlotsDAO.map(mapShiftAssignedTimeSlotDAOToModel);
    };
}
