import { BaseAdapter, ErrorCodes, ListOptions, NotFoundError } from '@humand-packages/common';
import { Service } from 'typedi';
import { v4 as uuidv4 } from 'uuid';
import { PortsDI } from '../../../../../config/portsDI';
import { LimitOffsetPagination } from '../../../../../types/listOptions';
import { AttachmentsService } from '../../../../attachments/business/services/attachmentsService';
import { SiteService } from '../../../../regions/business/services/sitesService';
import { User } from '../../../../users/business/models/user';
import { userDAOtoUser } from '../../../../users/infrastructure/mappers/usersMapper';
import ShiftsRepositoryPort from '../../../infrastructure/ports/shiftManagement/shiftRepositoryPort';
import { ShiftVC } from '../../../presentation/validationClasses/shiftManagement/shiftCreationVC';
import { ShiftManagementUsersFiltersVC } from '../../../presentation/validationClasses/shiftManagement/shiftManagementUsersFiltersVC';
import { ShiftsListFiltersVC } from '../../../presentation/validationClasses/shiftManagement/shiftsListFiltersVC';
import { ShiftTemplateVC } from '../../../presentation/validationClasses/shiftManagement/shiftTemplateVC';
import { mapShiftToPublicApiTemplate } from '../../mappers/shiftManagement/mapPublicApiShift';
import { PublicApiShiftTemplate } from '../../models/shiftManagement/publicApiShift';
import { Shift, ShiftForCreation, ShiftTimeSlotForCreation } from '../../models/shiftManagement/shift';
import { TimeSlot } from '../../models/timeTracking/scheduleData';
import { UsersServicePort } from '../../ports/userServicePort';
import { ShiftDefinitionInput } from '../../types/shiftManagement/shiftDefinitionInput';

const INITIAL_VERSION = 1;

@Service()
export class ShiftService extends BaseAdapter {
    constructor(
        @PortsDI(ShiftsRepositoryPort) private readonly shiftsRepository: ShiftsRepositoryPort,
        @PortsDI(UsersServicePort) private readonly usersService: UsersServicePort,
        private readonly attachmentsService: AttachmentsService,
        private readonly siteService: SiteService,
    ) {
        super();
    }

    public async getShifts(
        instanceId: number,
        filters: ShiftsListFiltersVC,
        pagination: LimitOffsetPagination,
    ): Promise<{ count: number; rows: Shift[] }> {
        const result = await this.shiftsRepository.getShifts(instanceId, filters, pagination);
        return {
            count: result.count,
            rows: result.rows,
        };
    }

    public async createShift(instanceId: number, saveAsTemplate: boolean, shift: ShiftVC): Promise<Shift> {
        const shiftForCreation = new ShiftForCreation({
            ...shift,
            code: uuidv4(),
            instanceId,
            isRest: saveAsTemplate ? false : (shift.isRest ?? false),
            isTemplate: saveAsTemplate,
            lastVersion: true,
            timeSlots: shift.timeSlots ? this.rawTimeSlotsToShiftTimeSlot(instanceId, shift.timeSlots) : [],
            version: INITIAL_VERSION,
        });

        return this.shiftsRepository.createShift(instanceId, shiftForCreation);
    }

    public async updateShiftTemplate(instanceId: number, shiftTemplate: ShiftTemplateVC, shiftTemplateId: number): Promise<Shift> {
        const existingShift = await this.shiftsRepository.getById(shiftTemplateId);
        // check is the template to handle the case where deleting a shift just sets isTemplate to false
        if (!existingShift || !existingShift.isTemplate) throw new NotFoundError('Shift not found', ErrorCodes.SHIFT_MANAGEMENT_SHIFT_NOT_FOUND);

        const newShiftForCreation = new ShiftForCreation({
            ...shiftTemplate,
            code: existingShift.code,
            instanceId,
            isRest: false,
            isTemplate: true,
            lastVersion: true,
            timeSlots: shiftTemplate.timeSlots ? this.rawTimeSlotsToShiftTimeSlot(instanceId, shiftTemplate.timeSlots) : [],
            version: existingShift.version + 1,
        });

        return this.shiftsRepository.updateShiftTemplate(instanceId, newShiftForCreation, existingShift);
    }

    public async removeFromTemplates(instanceId: number, shiftTemplateId: number): Promise<void> {
        const shiftTemplate = await this.shiftsRepository.getById(shiftTemplateId);
        if (!shiftTemplate) throw new NotFoundError('Shift not found', ErrorCodes.SHIFT_MANAGEMENT_SHIFT_NOT_FOUND);
        await this.shiftsRepository.removeFromTemplates(instanceId, shiftTemplateId);
    }

    public rawTimeSlotsToTimeSlot(timeSlots: [string, string][]): TimeSlot[] {
        return timeSlots.map(([startTime, endTime]) => ({ endTime, startTime }));
    }

    public rawTimeSlotsToShiftTimeSlot(instanceId: number, timeSlots: [string, string][]): ShiftTimeSlotForCreation[] {
        return timeSlots.map(([startTime, endTime]) => new ShiftTimeSlotForCreation({ endTime, instanceId, startTime }));
    }

    public async listUsers(
        loggedUser: User,
        hasManageCapability: boolean,
        params: ShiftManagementUsersFiltersVC,
        { pagination, sortingOptions }: ListOptions,
    ): Promise<{ count: number; rows: User[] }> {
        const emptyResult = { count: 0, rows: [] };
        let userIds = await this.usersService.getUserIds(loggedUser, params, hasManageCapability);
        if (!userIds.length) return emptyResult;

        if (params.siteId) {
            const site = await this.siteService.getById(params.siteId, loggedUser.getInstanceId());
            if (!site) return emptyResult;
            const siteUserIds = site.getUsers().map((user) => user.getUserId());
            userIds = userIds.filter((userId) => siteUserIds.includes(userId));
        }
        if (!userIds.length) return emptyResult;
        const { rows, count } = await this.usersService.listUsers(
            loggedUser,
            { ids: userIds },
            { pagination, sortingOptions },
            { excludeLoggedUser: false },
        );
        if (rows.length)
            await this.attachmentsService.addSignedUrlToModelWithImages(loggedUser.getInstanceId(), rows, ['profilePicture', 'coverPicture']);

        return {
            count,
            rows: rows.map(userDAOtoUser),
        };
    }

    public async getShiftById(id: number): Promise<Shift | null> {
        return this.shiftsRepository.getById(id);
    }

    public async getShiftTemplates(
        instanceId: number,
        pagination: LimitOffsetPagination,
    ): Promise<{ count: number; rows: PublicApiShiftTemplate[] }> {
        const result = await this.shiftsRepository.getShifts(instanceId, new ShiftsListFiltersVC(), pagination);
        return {
            count: result.count,
            rows: result.rows.map(mapShiftToPublicApiTemplate),
        };
    }

    public async getShiftsByCodes(instanceId: number, codes: string[]): Promise<Shift[]> {
        return this.shiftsRepository.getShiftsByCodes(instanceId, codes);
    }

    public async bumpShiftVersion(instanceId: number, existing: Shift, input: ShiftDefinitionInput): Promise<Shift> {
        const newForCreation = new ShiftForCreation({
            code: existing.code,
            color: input.color ?? null,
            description: input.description ?? null,
            instanceId,
            isRest: false,
            isTemplate: existing.isTemplate,
            lastVersion: true,
            name: input.name,
            timeSlots: this.rawTimeSlotsToShiftTimeSlot(instanceId, input.timeSlots),
            version: existing.version + 1,
        });
        return this.shiftsRepository.bumpShiftVersion(instanceId, newForCreation, existing);
    }

    public async deleteByCodes(instanceId: number, codes: string[]): Promise<void> {
        await this.shiftsRepository.deleteByCodes(instanceId, codes);
    }
}
