import { BaseAdapter, ErrorCodes, NotFoundError } from '@humand-packages/common';
import { DateTime } from 'luxon';
import { Service } from 'typedi';
import { Instance } from '../../../../instances/business/models/instance';
import { User } from '../../../../users/business/models/user';
import { AssignShiftRotationVC } from '../../../presentation/validationClasses/shiftManagement/assignShiftRotationVC';
import { ShiftRotationDayType } from '../../enum/shiftManagement/shiftRotationDayType';
import { ShiftCell } from '../../interfaces/shiftManagement/shiftCell';
import { Shift } from '../../models/shiftManagement/shift';
import { RawShiftAssignedShiftForCreation } from '../../models/shiftManagement/shiftAssignedShift';
import { ShiftRotation } from '../../models/shiftManagement/shiftRotation';
import { TimeSlot } from '../../models/timeTracking/scheduleData';
import { ShiftAssignedShiftStatus } from '../../types/shiftManagement/assignedShiftStatus';
import { ShiftAssignedShiftType } from '../../types/shiftManagement/assignedShiftType';
import { ShiftAssignedShiftSource } from '../../types/shiftManagement/source';
import ShiftAssignmentService from './shiftAssignment';
import { ShiftRotationService } from './shiftRotationService';

/**
 * Assigns a shift rotation onto users' calendars. Sits above the two peer services:
 * loads the rotation graph (`ShiftRotationService`), expands it into `ShiftCell[]`
 * and hands them to the source-agnostic stamping primitive
 * (`ShiftAssignmentService.assignShiftCells`). Fire-and-forget: no assignment entity is
 * persisted; each stamped shift carries rotation lineage. Recurrence is out of scope —
 * exactly one cycle is stamped, starting on `startDate` (rotation day position 1).
 */
@Service()
export class RotationAssignmentService extends BaseAdapter {
    constructor(
        private readonly shiftRotationService: ShiftRotationService,
        private readonly shiftAssignmentService: ShiftAssignmentService,
    ) {
        super();
    }

    public assignRotation = async (
        instance: Instance,
        user: User,
        rotationId: string,
        params: AssignShiftRotationVC,
        hasManageCapability: boolean,
    ): Promise<void> => {
        const { rotation, shiftsByCode } = await this.shiftRotationService.getById(instance.getId(), rotationId);
        const shiftCells = this.expand(instance.getId(), rotation, shiftsByCode, params.userIds, params.startDate);
        await this.shiftAssignmentService.assignShiftCells(instance, user, shiftCells, hasManageCapability);
    };

    /**
     * Maps the rotation's ordered days onto consecutive calendar dates from `startDate`
     * (day position 1 → startDate), for every user. WORK days emit one assigned shift per
     * rotation shift; REST days emit a single REST assigned shift. Every assigned shift
     * carries the rotation lineage (`shiftRotationId` + `shiftRotationDayPosition`).
     */
    private expand = (
        instanceId: number,
        rotation: ShiftRotation,
        shiftsByCode: Map<string, Shift>,
        userIds: number[],
        startDate: string,
    ): ShiftCell[] => {
        const start = DateTime.fromISO(startDate);
        const orderedDays = [...rotation.days].sort((a, b) => a.dayPosition - b.dayPosition);
        const shiftCells: ShiftCell[] = [];
        for (const userId of userIds) {
            orderedDays.forEach((day, index) => {
                const dateString = start.plus({ days: index }).toISODate()!;
                if (day.type === ShiftRotationDayType.WORK) {
                    const assignedShifts: RawShiftAssignedShiftForCreation[] = [];
                    const timeSlots: TimeSlot[] = [];
                    for (const dayShift of day.shifts) {
                        const shift = shiftsByCode.get(dayShift.shiftCode);
                        if (!shift) {
                            throw new NotFoundError('Shift not found', ErrorCodes.SHIFT_MANAGEMENT_SHIFT_NOT_FOUND);
                        }
                        assignedShifts.push({
                            instanceId,
                            shiftCode: shift.code,
                            shiftRotationDayPosition: day.dayPosition,
                            shiftRotationId: rotation.id,
                            shiftVersion: shift.version,
                            source: ShiftAssignedShiftSource.APP,
                            status: ShiftAssignedShiftStatus.PUBLISHED,
                            type: ShiftAssignedShiftType.WORK,
                        });
                        timeSlots.push(...(shift.timeSlots as TimeSlot[]));
                    }
                    shiftCells.push({ assignedShifts, dateString, instanceId, isRest: false, timeSlots, userId });
                } else {
                    shiftCells.push({
                        assignedShifts: [
                            {
                                instanceId,
                                shiftRotationDayPosition: day.dayPosition,
                                shiftRotationId: rotation.id,
                                source: ShiftAssignedShiftSource.APP,
                                status: ShiftAssignedShiftStatus.PUBLISHED,
                                type: ShiftAssignedShiftType.REST,
                            },
                        ],
                        dateString,
                        instanceId,
                        isRest: true,
                        timeSlots: [],
                        userId,
                    });
                }
            });
        }
        return shiftCells;
    };
}
