import { BaseAdapter, ErrorCodes, LoggerPort, NotFoundError } from '@humand-packages/common';
import { Service } from 'typedi';
import { PortsDI } from '../../../../../config/portsDI';
import { AttachmentsService } from '../../../../attachments/business/services/attachmentsService';
import { UserBotApp } from '../../../../botApps/business/models/botApp';
import { FaceSearchResultErrorCause } from '../../../../facialRecognition/business/interfaces/faceSearchResultErrorCause';
import FaceRecognitionServicePort from '../../../../facialRecognition/business/ports/faceRecognitionPort';
import { FeatureFlagCode } from '../../../../featureFlags/business/enums/featureFlagCode';
import { FeatureFlagService } from '../../../../featureFlags/business/services/featureFlagService';
import { Instance } from '../../../../instances/business/models/instance';
import { SiteService } from '../../../../regions/business/services/sitesService';
import { UsersService } from '../../../../users/business/services/usersService';
import TimeTrackingKioskRepositoryPort from '../../../infrastructure/ports/TimeTrackingKioskRepositoryPort';
import { UserBasicDataDTO } from '../../../presentation/dtos/userBasicDataDTO';
import { UserBasicDataSC } from '../../../presentation/serializationClasses/userBasicDataSC';
import { FaceClockResult } from '../../interfaces/kiosk/faceClockResult';
import TimeTrackingService from '../timeTracking';
import TimeTrackingFaceAssignmentService from '../timeTracking/timeTrackingFaceAssignment';

@Service()
export default class TimeTrackingKioskFaceSearchService extends BaseAdapter {
    constructor(
        @PortsDI(TimeTrackingKioskRepositoryPort) private readonly timeTrackingKioskRepository: TimeTrackingKioskRepositoryPort,
        @PortsDI(FaceRecognitionServicePort) private readonly faceRecognitionService: FaceRecognitionServicePort,
        @PortsDI(TimeTrackingService) private readonly timeTrackingService: TimeTrackingService,
        private readonly usersService: UsersService,
        private readonly attachmentsService: AttachmentsService,
        private readonly siteService: SiteService,
        private readonly faceAssignmentService: TimeTrackingFaceAssignmentService,
        private readonly logger: LoggerPort,
        private readonly featureFlagService: FeatureFlagService,
    ) {
        super();
    }

    public async faceClock(instance: Instance, loggedBotApp: UserBotApp, file: Express.Multer.File): Promise<FaceClockResult> {
        const kiosk = await this.timeTrackingKioskRepository.getByBotAppId(loggedBotApp.id, instance.getId());
        if (!kiosk) throw new NotFoundError('Kiosk not found', ErrorCodes.NOT_FOUND);
        const collectionId = kiosk.siteId
            ? this.faceAssignmentService.getSiteCollectionId(instance.getId(), kiosk.siteId!)
            : instance.getId().toString();
        const result = await this.faceRecognitionService.searchFace(instance.getId(), collectionId, file);
        if (result.found) {
            const userId = parseInt(result.externalImageId!);
            const user = await this.usersService.findByPk(instance.getId(), userId);
            if (!user || user.instanceId !== instance.getId()) throw new NotFoundError('User not found', ErrorCodes.NOT_FOUND);
            if (kiosk.siteId) {
                const { siteUserIds, siteName } = await this.getSiteContext(instance.getId(), kiosk.siteId);
                if (!siteUserIds.includes(user.getId())) {
                    const userFaceConfig = await this.faceRecognitionService
                        .getConfigurations(instance.getId(), [userId])
                        .then((configs) => configs[0])
                        .catch(() => undefined);
                    this.logger.warn(
                        {
                            context: {
                                collectionId,
                                error: FaceSearchResultErrorCause.NO_MATCHES_FOUND,
                                instanceId: instance.getId(),
                                kioskId: kiosk.id,
                                kioskName: kiosk.name,
                                matchedUserFaceId: userFaceConfig?.faceId,
                                matchedUserId: user.getId(),
                                siteId: kiosk.siteId,
                                siteName,
                                siteUsers: siteUserIds,
                            },
                        },
                        'Face clock: user matched in AWS collection but not found in kiosk site users',
                    );
                    result.cause = FaceSearchResultErrorCause.NO_MATCHES_FOUND;
                    result.found = false;
                    result.matchedUser = null;
                    return { searchResult: result, timeTrackingEntry: null };
                }
            }
            const hasTimeTrackingDesertEagleEnabledFeatureFlag = await this.getTimeTrackingDesertEagleEnabledFeatureFlag(instance.getId());
            const timeTrackingEntry = await this.timeTrackingService.faceClock(
                instance,
                user,
                loggedBotApp,
                hasTimeTrackingDesertEagleEnabledFeatureFlag,
            );
            await this.attachmentsService.addSignedUrlToModelWithImages(instance.getId(), [user], ['profilePicture', 'coverPicture']);
            return { searchResult: { ...result, matchedUser: new UserBasicDataSC().serialize(user) }, timeTrackingEntry };
        } else {
            const siteContext = kiosk.siteId ? await this.getSiteContext(instance.getId(), kiosk.siteId).catch(() => null) : null;
            const faceConfigs = siteContext?.siteUserIds?.length
                ? await this.faceRecognitionService.getConfigurations(instance.getId(), siteContext.siteUserIds).catch(() => [])
                : [];
            this.logger.warn(
                {
                    context: {
                        collectionId,
                        error: result.cause,
                        extraInfo: result.extraInfo,
                        instanceId: instance.getId(),
                        kioskId: kiosk.id,
                        kioskName: kiosk.name,
                        siteId: kiosk.siteId,
                        siteName: siteContext?.siteName,
                        siteUsers: siteContext?.siteUserIds,
                        siteUsersTotal: siteContext?.siteUserIds?.length ?? 0,
                        siteUsersWithFaceConfigured: faceConfigs.filter((c) => c.hasReferenceImage).length,
                    },
                },
                'Face clock search failed',
            );
            if (result.cause === FaceSearchResultErrorCause.MULTIPLE_FACES_FOUND) {
                const users = await this.usersService.findByPks(
                    result.multipleFacesExternalImageIds!.map((id) => parseInt(id)),
                    { instanceId: instance.getId() },
                );
                await this.attachmentsService.addSignedUrlToModelWithImages(instance.getId(), users, ['profilePicture']);
                return {
                    searchResult: {
                        ...result,
                        matchedUser: null,
                        multipleFacesMatchedUsers: users.map((user) => new UserBasicDataSC().serialize(user)),
                    },
                    timeTrackingEntry: null,
                };
            } else {
                return { searchResult: { ...result, matchedUser: null }, timeTrackingEntry: null };
            }
        }
    }

    private getSiteContext = async (instanceId: number, siteId: number): Promise<{ siteUserIds: number[]; siteName: string | null }> => {
        const site = await this.siteService.getById(siteId, instanceId);
        if (!site) return { siteName: null, siteUserIds: [] };
        return {
            siteName: site.getName(),
            siteUserIds: site.getUsers().map((user) => user.getUserId()),
        };
    };

    private getTimeTrackingDesertEagleEnabledFeatureFlag = async (instanceId: number): Promise<boolean> => {
        const featureFlag = await this.featureFlagService.getBooleanFeatureFlag(FeatureFlagCode.TIME_TRACKING_DESERT_EAGLE_ENABLED, instanceId);
        return featureFlag.value;
    };

    public async getKioskUsers(instanceId: number, userIds: number[]): Promise<UserBasicDataDTO[]> {
        if (!userIds || userIds.length === 0) {
            return [];
        }
        const users = await this.usersService.findByPks(userIds, { instanceId });
        await this.attachmentsService.addSignedUrlToModelWithImages(instanceId, users, ['profilePicture']);
        return users.map((user) => new UserBasicDataSC().serialize(user));
    }
}
