import { ENV_OPTIONAL_NUMBER_KEYS_MAPPING, SEQUELIZE_INSTANCE_TOKEN } from '@humand-packages/common';
import { LimitOffsetPagination } from 'humand-packages/monolith/src/api/types/listOptions';
import { Op, fn, col, where as sequelizeWhere, literal } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { Inject, Service } from 'typedi';
import { BaseRepositoryAdapter } from '../../../../../../dbAdapters/baseRepositoryAdapter';

import { TimeTrackingKioskUser, TimeTrackingKioskUserForCreation } from '../../../../business/models/timeTracking/timeTrackingKioskUser';
import { TimeTrackingKioskUserDAO } from '../../../dataAccessObjects/timeTracking/timeTrackingKioskUser';
import {
    mapTimeTrackingKioskUserDAOToModel,
    mapTimeTrackingKioskUserForCreationToCreationAttributes,
} from '../../../mappers/timeTracking/timeTrackingKioskUserMapper';
import TimeTrackingKioskUserRepositoryPort from '../../../ports/timeTracking/timeTrackingKioskUserPort';

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

    public bulkCreate = async (kioskUsers: TimeTrackingKioskUserForCreation[]): Promise<TimeTrackingKioskUser[]> => {
        const withoutSite = kioskUsers.filter((kioskUser) => kioskUser.siteId === null);
        const withSite = kioskUsers.filter((kioskUser) => kioskUser.siteId !== null);

        const results: TimeTrackingKioskUser[] = [];

        if (withoutSite.length > 0) {
            const created = await TimeTrackingKioskUserDAO.bulkCreate(withoutSite.map(mapTimeTrackingKioskUserForCreationToCreationAttributes), {
                conflictAttributes: ['userId'],
                conflictWhere: { siteId: null },
                updateOnDuplicate: ['updatedAt', 'deletedAt'],
            });
            results.push(...created.map(mapTimeTrackingKioskUserDAOToModel));
        }

        if (withSite.length > 0) {
            const created = await TimeTrackingKioskUserDAO.bulkCreate(withSite.map(mapTimeTrackingKioskUserForCreationToCreationAttributes), {
                conflictAttributes: ['userId', 'siteId'],
                conflictWhere: { siteId: { [Op.not]: null } },
                updateOnDuplicate: ['updatedAt', 'deletedAt'],
            });
            results.push(...created.map(mapTimeTrackingKioskUserDAOToModel));
        }

        return results;
    };

    public bulkDeleteBySiteId = async (instanceId: number, siteId: number, userIds?: number[]): Promise<void> => {
        await TimeTrackingKioskUserDAO.destroy({ where: { instanceId, siteId, ...(userIds && { userId: userIds }) } });
    };

    public bulkDeleteByRegionId = async (instanceId: number, regionId: number, userIds?: number[]): Promise<void> => {
        await TimeTrackingKioskUserDAO.destroy({ where: { instanceId, regionId, ...(userIds && { userId: userIds }) } });
    };

    public bulkDeleteByUserIds = async (instanceId: number, userIds: number[]): Promise<void> => {
        await TimeTrackingKioskUserDAO.destroy({ where: { instanceId, userId: userIds } });
    };

    public getChangedByDate = async (
        instanceId: number,
        siteId: number | null,
        since: Date,
        pagination: LimitOffsetPagination,
    ): Promise<{ count: number; rows: TimeTrackingKioskUser[] }> => {
        const effectiveChangeTime = fn('GREATEST', col('updatedAt'), fn('COALESCE', col('deletedAt'), col('updatedAt')));
        const { count, rows } = await TimeTrackingKioskUserDAO.findAndCountAll({
            limit: pagination.limit,
            offset: pagination.offset,
            order: [
                [literal('GREATEST("updatedAt", COALESCE("deletedAt", "updatedAt"))'), 'ASC'],
                ['id', 'ASC'],
            ],
            paranoid: false,
            where: {
                instanceId,
                ...(siteId !== null ? { siteId } : { siteId: null }),
                [Op.and]: sequelizeWhere(effectiveChangeTime, { [Op.gte]: since }),
            },
        });
        return { count, rows: rows.map(mapTimeTrackingKioskUserDAOToModel) };
    };

    public getUserIdsBySiteOrGlobal = async (
        instanceId: number,
        siteId: number | null,
        pagination: LimitOffsetPagination,
    ): Promise<{ count: number; rows: TimeTrackingKioskUser[] }> => {
        const { count, rows } = await TimeTrackingKioskUserDAO.findAndCountAll({
            limit: pagination.limit,
            offset: pagination.offset,
            order: [
                ['userId', 'ASC'],
                ['id', 'ASC'],
            ],
            paranoid: false,
            where: { instanceId, ...(siteId !== null ? { siteId } : { siteId: null }) },
        });
        return { count, rows: rows.map(mapTimeTrackingKioskUserDAOToModel) };
    };
}
