import { ENV_OPTIONAL_NUMBER_KEYS_MAPPING, ErrorCodes, NotFoundError, SEQUELIZE_INSTANCE_TOKEN, UtilsPort } from '@humand-packages/common';
import { Op } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { Inject, Service } from 'typedi';
import { BaseRepositoryAdapter } from '../../../../../../dbAdapters/baseRepositoryAdapter';
import { CategorizedHourStatusOverride } from '../../../../business/models/timeTracking/categorizedHourStatusOverride';
import {
    ActiveCategorizedHourSummary,
    CategorizedHourDataUpdate,
    SavedPendingCategorizedHour,
    TimeTrackingCategorizedHourAttributes,
    TimeTrackingCategorizedHourForCreation,
} from '../../../../business/models/timeTracking/timeTrackingCategorizedHour';
import { TimeTrackingCategorizedHourStatus } from '../../../constants/timeTracking/categorizedHourStatus';
import { TimeTrackingCategorizedHourDAO } from '../../../dataAccessObjects/hourCategory/categorizedHour';
import { TimeTrackingCategorizedHourRepositoryPort } from '../../../ports/timeTracking/timeTrackingCategorizedHourRepositoryPort';

@Service()
export default class TimeTrackingCategorizedHourRepositoryAdapter extends BaseRepositoryAdapter implements TimeTrackingCategorizedHourRepositoryPort {
    constructor(
        protected readonly utilsPort: UtilsPort,
        @Inject(ENV_OPTIONAL_NUMBER_KEYS_MAPPING.BULK_CREATE_CHUNK_SIZE) bulkCreateChunkSize: number,
        @Inject(SEQUELIZE_INSTANCE_TOKEN) sequelize: Sequelize,
    ) {
        super(bulkCreateChunkSize, sequelize);
    }

    public bulkCreateCategorizedHours = async (
        categorizedHours: TimeTrackingCategorizedHourForCreation[],
    ): Promise<SavedPendingCategorizedHour[]> => {
        if (categorizedHours.length === 0) return [];
        const categorizedHoursToCreate: TimeTrackingCategorizedHourAttributes[] = categorizedHours.map((categorizedHour) => ({
            ...categorizedHour,
            id: this.utilsPort.generateUUIDv7(),
        }));
        await TimeTrackingCategorizedHourDAO.bulkCreate(categorizedHoursToCreate, {
            ignoreDuplicates: true,
        });
        const attemptedIds = categorizedHoursToCreate.map((h) => h.id);
        const inserted = await TimeTrackingCategorizedHourDAO.findAll({
            attributes: ['id', 'userId'],
            raw: true,
            where: { id: attemptedIds },
        });
        return inserted.map((dao) => ({ id: dao.id, userId: dao.userId }));
    };

    public existsCategorizedHour = async (id: string, instanceId: number): Promise<boolean> => {
        const count = await TimeTrackingCategorizedHourDAO.count({ where: { id, instanceId } });
        return count > 0;
    };

    public isFrozenByAdminRejection = async (id: string, instanceId: number): Promise<boolean> => {
        const count = await TimeTrackingCategorizedHourDAO.count({
            where: {
                id,
                instanceId,
                status: TimeTrackingCategorizedHourStatus.REJECTED,
                statusOverriddenByAdminId: { [Op.ne]: null },
            },
        });
        return count > 0;
    };

    public updateCategorizedHourStatus = async (
        id: string,
        instanceId: number,
        status: TimeTrackingCategorizedHourStatus,
        override: CategorizedHourStatusOverride | null = null,
    ): Promise<void> => {
        await TimeTrackingCategorizedHourDAO.update(
            {
                status,
                statusOverriddenByAdminId: override?.adminId ?? null,
                statusOverrideComment: override?.comment ?? null,
            },
            { where: { id, instanceId } },
        );
    };

    public bulkDeleteCategorizedHours = async (
        instanceId: number,
        userIds: number[],
        hourCategoryId: number,
        startDate: string,
        endDate: string,
        status: TimeTrackingCategorizedHourStatus,
    ): Promise<void> => {
        await TimeTrackingCategorizedHourDAO.destroy({
            where: {
                dateString: { [Op.between]: [startDate, endDate] },
                hourCategoryId,
                instanceId,
                status,
                userId: { [Op.in]: userIds },
            },
        });
    };

    /** Returns all active categorized hours with approval status for the given instance, user, and hour category.
     * Active categorized hours are those that are either PENDING, APPROVED, or REJECTED.
     * Admin-overridden hours (statusOverriddenByAdminId != null) are intentionally excluded:
     *  once an admin has overridden a status, retroactive recalculations must not touch it. */
    public findActiveCategorizedHoursWithApproval = async (
        instanceId: number,
        userIds: number[],
        hourCategoryIds: number[],
        startDate: string,
        endDate: string,
    ): Promise<ActiveCategorizedHourSummary[]> => {
        const daos = await TimeTrackingCategorizedHourDAO.findAll({
            attributes: ['id', 'userId', 'dateString', 'hourCategoryId', 'hours', 'status'],
            where: {
                dateString: { [Op.between]: [startDate, endDate] },
                hourCategoryId: { [Op.in]: hourCategoryIds },
                instanceId,
                status: {
                    [Op.in]: [
                        TimeTrackingCategorizedHourStatus.PENDING,
                        TimeTrackingCategorizedHourStatus.APPROVED,
                        TimeTrackingCategorizedHourStatus.REJECTED,
                    ],
                },
                statusOverriddenByAdminId: null,
                userId: { [Op.in]: userIds },
            },
            raw: true,
        });
        return daos.map((dao) => ({
            dateString: dao.dateString,
            hourCategoryId: dao.hourCategoryId,
            hours: dao.hours,
            id: dao.id,
            status: dao.status as TimeTrackingCategorizedHourStatus,
            userId: dao.userId,
        }));
    };

    public bulkUpdateCategorizedHoursStatusForUsersInRange = async (
        instanceId: number,
        userIds: number[],
        hourCategoryIds: number[],
        startDate: string,
        endDate: string,
        currentStatuses: TimeTrackingCategorizedHourStatus[],
        newStatus: TimeTrackingCategorizedHourStatus,
    ): Promise<void> => {
        if (userIds.length === 0 || hourCategoryIds.length === 0 || currentStatuses.length === 0) {
            return;
        }
        await TimeTrackingCategorizedHourDAO.update(
            { status: newStatus, statusOverriddenByAdminId: null },
            {
                where: {
                    dateString: { [Op.between]: [startDate, endDate] },
                    hourCategoryId: { [Op.in]: hourCategoryIds },
                    instanceId,
                    status: { [Op.in]: currentStatuses },
                    statusOverriddenByAdminId: null,
                    userId: { [Op.in]: userIds },
                },
            },
        );
    };

    public bulkUpdateCategorizedHoursData = async (updates: CategorizedHourDataUpdate[], instanceId: number): Promise<void> => {
        if (updates.length === 0) return;
        await Promise.all(
            updates.map((update) =>
                TimeTrackingCategorizedHourDAO.update(
                    { entryIds: update.entryIds ?? [], hours: update.hours },
                    { where: { id: update.id, instanceId } },
                ),
            ),
        );
    };

    public deleteCategorizedHoursByIds = async (ids: string[]): Promise<void> => {
        if (ids.length === 0) return;
        await TimeTrackingCategorizedHourDAO.destroy({ where: { id: { [Op.in]: ids } } });
    };

    public getCategorizedHourStatus = async (id: string, instanceId: number): Promise<TimeTrackingCategorizedHourStatus> => {
        const dao = await TimeTrackingCategorizedHourDAO.findOne({
            attributes: ['status'],
            where: { id, instanceId },
        });
        if (!dao) throw new NotFoundError('Categorized hour not found', ErrorCodes.TIME_TRACKING_CATEGORIZED_HOUR_NOT_FOUND);
        return dao.status as TimeTrackingCategorizedHourStatus;
    };

    public updateCategorizedHourValue = async (id: string, instanceId: number, hours: number): Promise<void> => {
        // Writes ONLY the `hours` column. Status, entryIds, overrides, note are never touched.
        // The (id, instanceId) WHERE provides defense-in-depth tenant scoping.
        await TimeTrackingCategorizedHourDAO.update({ hours }, { where: { id, instanceId } });
    };
}
