import {
    BadRequestError,
    BaseController,
    ClientScopes,
    ENV_OPTIONAL_NUMBER_KEYS_MAPPING,
    ErrorCodes,
    ForbiddenError,
    LoggerPort,
} from '@humand-packages/common';
import { CommunityFeatureService } from '@humand-packages/community-features';
import { NextFunction, Request } from 'express';
import { CREATED, NO_CONTENT, OK } from 'http-status-codes';
import { Inject, Service } from 'typedi';
import { PortsDI } from '../../../../config/portsDI';
import { DeprecatedHumandResponse } from '../../../../types/locals';
import { FeatureFlagCode } from '../../../featureFlags/business/enums/featureFlagCode';
import { FeatureFlagService } from '../../../featureFlags/business/services/featureFlagService';
import { PermissionsService } from '../../../permissions/business/services/permissionsService';
import { UserDAO } from '../../../users/infrastructure/dataAccessObjects/userDAO';
import { userDAOtoUser } from '../../../users/infrastructure/mappers/usersMapper';
import { UserListPaginatedSerializer } from '../../../users/presentation/serializationClasses/userListPaginatedSerializer';
import { TimeTrackingKioskPort } from '../../business/ports/timeTrackingKioskPort';
import TimeTrackingKioskFaceSearchService from '../../business/services/kiosk/kioskFaceSearchService';
import { TimeTrackingIncidenceNotificationService } from '../../business/services/timeTracking/timeTrackingIncidenceNotificationService';
import { TimeTrackingKioskStatus } from '../../business/types/kioskStatus';
import { TimeTrackingKiosksSC } from '../serializationClasses/TimeTrackingKiosksSC';
import ListKioskCollabsVC from '../validationClasses/listKioskCollabsVC';
import ListTimeTrackingKiosksVC from '../validationClasses/listTimeTrackingKiosksVC';
import { TimeTrackingKioskPinVerifyVC } from '../validationClasses/timeTracking/timeTrackingKioskPinVerifyVC';
import { TimeTrackingKiosksCreationVC } from '../validationClasses/timeTrackingKiosksCreationVC';
import { TimeTrackingKiosksUpdateVC } from '../validationClasses/timeTrackingKiosksUpdateVC';

const MAX_FILE_SIZE = 5 * 1024 * 1024;
@Service()
export class TimeTrackingKioskController extends BaseController {
    constructor(
        @PortsDI(TimeTrackingKioskPort) private readonly timeTrackingKioskService: TimeTrackingKioskPort,
        private readonly permissionService: PermissionsService,
        @Inject(ENV_OPTIONAL_NUMBER_KEYS_MAPPING.NOTIFICATION_DELAY) notificationDelay: number,
        logger: LoggerPort,
        private readonly kioskFaceSearchService: TimeTrackingKioskFaceSearchService,
        private readonly featureFlagService: FeatureFlagService,
        private readonly communityFeatureService: CommunityFeatureService,
        private readonly incidenceNotificationService: TimeTrackingIncidenceNotificationService,
    ) {
        super(logger, notificationDelay);
    }

    public getById = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const kiosk = await this.timeTrackingKioskService.getByIdOrFail(user, id);
            res.json(kiosk);
            res.status(OK).end();
        });
    };

    public list = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { loggedUser, requiredLimitOffsetPagination } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const filters = res.locals.queryParams as ListTimeTrackingKiosksVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const { count, rows } = await this.timeTrackingKioskService.list(
                user,
                { ...filters, instanceId: loggedUser.instanceId },
                { ...requiredLimitOffsetPagination },
            );
            res.json(
                new TimeTrackingKiosksSC().serializePaginatedList({
                    count,
                    limit: requiredLimitOffsetPagination.limit,
                    offset: requiredLimitOffsetPagination.offset,
                    rows,
                }),
            );
            res.status(OK).end();
        });
    };

    public create = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = res.locals.bodyParams as TimeTrackingKiosksCreationVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const newKiosk = await this.timeTrackingKioskService.create(user, {
                description: params.description,
                faceRecognitionBlocking: params.faceRecognitionBlocking,
                faceRecognitionEnabled: params.faceRecognitionEnabled,
                instanceId: loggedUser.instanceId,
                name: params.name,
                pinAuthenticationEnabled: params.pinAuthenticationEnabled ?? false,
                siteId: params.siteId ?? null,
                status: TimeTrackingKioskStatus.UNLINKED,
            });
            res.json(newKiosk);
            res.status(OK).end();
        });
    };

    public update = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = res.locals.bodyParams as TimeTrackingKiosksUpdateVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const updated = await this.timeTrackingKioskService.update(user, id, {
                ...params,
            });
            res.json(updated);
            res.status(OK).end();
        });
    };

    public eliminate = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            await this.timeTrackingKioskService.eliminate(id, loggedUser.instanceId);
            res.status(NO_CONTENT).end();
        });
    };

    public listCollabs = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedBotApp, requiredLimitOffsetPagination, loggedBotApp } = res.locals;
        if (!instanceOfLoggedBotApp || !loggedBotApp)
            throw new ForbiddenError('The provided bot app has no access to this resource', ErrorCodes.ACCESS_DENIED);
        const filters = res.locals.queryParams as ListKioskCollabsVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            const { count, rows } = await this.timeTrackingKioskService.listCollabs(
                instanceOfLoggedBotApp.getId(),
                { ...filters, instanceId: instanceOfLoggedBotApp.getId() },
                { ...requiredLimitOffsetPagination },
                loggedBotApp!.id,
            );
            res.status(OK).json(
                new UserListPaginatedSerializer().serializePaginatedList(
                    rows,
                    requiredLimitOffsetPagination.limit as number,
                    requiredLimitOffsetPagination.offset as number,
                    count,
                    { instance: instanceOfLoggedBotApp },
                ),
            );
            res.status(OK).end();
        });
    };

    public settings = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedBotApp, loggedBotApp } = res.locals;
        if (!loggedBotApp || !instanceOfLoggedBotApp)
            throw new ForbiddenError('The provided bot app has no access to this resource', ErrorCodes.ACCESS_DENIED);
        await this.endpointHandlerWithGenerics(next, async () => {
            const communityFeatures = await this.communityFeatureService.getCommunityFeaturesForScope(
                instanceOfLoggedBotApp.getId(),
                ClientScopes.APP,
            );
            const featureFlags = await this.featureFlagService.getAllFeatureFlags(instanceOfLoggedBotApp.getId());
            const kiosk = await this.timeTrackingKioskService.getByBotAppIdOrFail(loggedBotApp.id, instanceOfLoggedBotApp.getId());
            const kioskTouchlessEnabled = await this.featureFlagService.getBooleanFeatureFlag(
                FeatureFlagCode.KIOSK_TOUCHLESS_ENABLED,
                instanceOfLoggedBotApp.getId(),
            );
            res.status(OK).json({
                communityFeatures,
                featureFlags,
                instance: instanceOfLoggedBotApp,
                kiosk,
                kioskTouchlessEnabled: kioskTouchlessEnabled.value,
            });
            res.status(OK).end();
        });
    };

    public unLink = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const updated = await this.timeTrackingKioskService.unLink(id, loggedUser.instanceId);
            res.json(updated);
            res.status(OK).end();
        });
    };

    public verifyPin = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedBotApp, loggedBotApp } = res.locals;
        if (!loggedBotApp || !instanceOfLoggedBotApp)
            throw new ForbiddenError('The provided bot app has no access to this resource', ErrorCodes.ACCESS_DENIED);
        const { kioskPin } = res.locals.bodyParams as TimeTrackingKioskPinVerifyVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            const user = await this.timeTrackingKioskService.verifyPin(instanceOfLoggedBotApp.getId(), loggedBotApp.id, kioskPin);
            res.json(user);
            res.status(OK).end();
        });
    };

    public faceClock = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedBotApp, loggedBotApp } = res.locals;
        if (!loggedBotApp || !instanceOfLoggedBotApp)
            throw new ForbiddenError('The provided bot app has no access to this resource', ErrorCodes.ACCESS_DENIED);
        const { file } = _req;
        await this.endpointHandlerWithGenerics(
            next,
            async () => {
                if (!file) throw new BadRequestError('No file uploaded', ErrorCodes.INVALID_DATA);
                if (file.size > MAX_FILE_SIZE) throw new BadRequestError('File size should be less than 5MB', ErrorCodes.INVALID_DATA);
                const result = await this.kioskFaceSearchService.faceClock(instanceOfLoggedBotApp, loggedBotApp, file);
                res.status(CREATED).json(result);
                return { createdEntry: result.timeTrackingEntry, instance: instanceOfLoggedBotApp };
            },
            async ({ createdEntry, instance }) => {
                if (createdEntry) await this.incidenceNotificationService.evaluateEntry(createdEntry, instance);
            },
        );
    };
}
