import { BaseController, ENV_OPTIONAL_NUMBER_KEYS_MAPPING, ISO_DATE, LoggerPort } from '@humand-packages/common';
import { NextFunction, Request } from 'express';
import { NO_CONTENT, OK } from 'http-status-codes';
import moment from 'moment';
import { Inject, Service } from 'typedi';
import { PortsDI } from '../../../../config/portsDI';
import { DeprecatedHumandResponse } from '../../../../types/locals';
import { PermissionsService } from '../../../permissions/business/services/permissionsService';
import { GROUP_MEMBER_POLICIES } from '../../../regions/business/types/regions';
import { User } from '../../../users/business/models/user';
import { UsersService } from '../../../users/business/services/usersService';
import { UserDAO } from '../../../users/infrastructure/dataAccessObjects/userDAO';
import { userDAOtoUser } from '../../../users/infrastructure/mappers/usersMapper';
import {
    DEFAULT_AUTO_CLOSE_FIXED_HOURS_VALUE,
    DEFAULT_AUTO_CLOSE_FIXED_MINUTES_VALUE,
    DEFAULT_AUTO_CLOSE_LIMIT_HOURS,
    DEFAULT_AUTO_CLOSE_MODE,
} from '../../business/constants/timeTrackingPolicy';
import { TimeTrackingPolicyAutoCloseMode } from '../../business/enum/timeTracking/policy';
import { TimeTrackingPoliciesPort } from '../../business/ports/timeTrackingPoliciesPort';
import { TimeTrackingHourCategorizationService } from '../../business/services/hourCategorizationService';
import { TimeTrackingPoliciesSC } from '../serializationClasses/TimeTrackingPoliciesSC';
import { TimeTrackingGlobalPolicyStepApproversSC } from '../serializationClasses/timeTrackingPolicyApprovalStepsSC';
import { TimeTrackingPolicyHourCategoriesSC } from '../serializationClasses/TimeTrackingPolicyHourCategoriesSC';
import AssignUserPoliciesVC from '../validationClasses/assignUserPolicies';
import { GetHourCategoriesVC } from '../validationClasses/hourCategorization/GetHourCategoriesVC';
import ListTimeTrackingPoliciesVC from '../validationClasses/listTimeTrackingPoliciesVC';
import { TimeTrackingPoliciesCreationVC } from '../validationClasses/timeTrackingPoliciesCreationVC';
import { TimeTrackingPoliciesUpdateVC } from '../validationClasses/timeTrackingPoliciesUpdateVC';
import { UpdateTimeTrackingApprovalUsersVC, UpdateTimeTrackingPolicyApprovalUsersVC } from '../validationClasses/updateTimeTrackingApprovalUsersVC';

@Service()
export class TimeTrackingPoliciesController extends BaseController {
    constructor(
        @PortsDI(TimeTrackingPoliciesPort) private readonly timeTrackingPoliciesService: TimeTrackingPoliciesPort,
        private readonly permissionService: PermissionsService,
        private readonly usersService: UsersService,
        private readonly hourCategorizationService: TimeTrackingHourCategorizationService,
        @Inject(ENV_OPTIONAL_NUMBER_KEYS_MAPPING.NOTIFICATION_DELAY) notificationDelay: number,
        logger: LoggerPort,
    ) {
        super(logger, notificationDelay);
    }

    public getPolicyByUser = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedUser, loggedUser } = res.locals;
        const date = moment.tz(instanceOfLoggedUser.getTimezone()).format(ISO_DATE);
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewTimeTracking(user);
            const { count: _, rows } = await this.timeTrackingPoliciesService.listPolicies(
                { fromDate: date, instanceId: loggedUser.instanceId, toDate: date, userIds: [loggedUser.id] },
                { limit: 1, offset: 0 },
            );
            res.json(rows.length ? rows[0] : null);
            res.status(OK).end();
        });
    };

    public getPolicyById = 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 policy = await this.timeTrackingPoliciesService.getPolicyByIdOrFail(user, id);
            res.status(OK).json(policy);
        });
    };

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

    public createPolicy = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { instanceOfLoggedUser, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = res.locals.bodyParams as TimeTrackingPoliciesCreationVC;
        const date = moment.tz(instanceOfLoggedUser.getTimezone()).format(ISO_DATE);
        let users: number[] = [];
        const allUsers = params.userMode === GROUP_MEMBER_POLICIES.ALL_USERS;
        if (params.userMode === GROUP_MEMBER_POLICIES.TARGETED_USERS) {
            users = params.collaborators!;
        } else if (allUsers) {
            users = await this.usersService.getUserIdsInInstance(loggedUser.instanceId);
        }
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const fromDate = params.fromDate ?? date;
            const policyHourCategories = params.policyHourCategories?.map((policyHourCategory) => ({
                hourCategoryId: policyHourCategory.hourCategoryId,
                requiresApproval: policyHourCategory.requiresApproval,
            }));
            const newPolicy = await this.timeTrackingPoliciesService.createAndAssociatePolicy(
                (user as User).instanceId,
                {
                    allUsers,
                    autoCloseFixedHoursValue:
                        params.autoCloseMode === TimeTrackingPolicyAutoCloseMode.FIXED_HOURS
                            ? (params.autoCloseFixedHoursValue ?? DEFAULT_AUTO_CLOSE_FIXED_HOURS_VALUE)
                            : null,
                    autoCloseFixedMinutesValue:
                        params.autoCloseMode === TimeTrackingPolicyAutoCloseMode.FIXED_HOURS
                            ? (params.autoCloseFixedMinutesValue ?? DEFAULT_AUTO_CLOSE_FIXED_MINUTES_VALUE)
                            : null,
                    autoCloseLimitHours: params.autoCloseLimitHours ?? DEFAULT_AUTO_CLOSE_LIMIT_HOURS,
                    autoCloseMode: params.autoCloseMode ?? DEFAULT_AUTO_CLOSE_MODE,
                    clockingInAppEnabled: params.clockingInAppEnabled,
                    clockingInKioskEnabled: params.clockingInKioskEnabled ?? false,
                    clockingInWebEnabled: params.clockingInWebEnabled ?? false,
                    description: params.description,
                    faceRecognitionBlocking: params.faceRecognitionBlocking,
                    faceRecognitionEnabled: params.faceRecognitionEnabled,
                    instanceId: loggedUser.instanceId,
                    latenessTolerance: params.latenessTolerance,
                    name: params.name,
                    nightShift: params.nightShift ?? false,
                    notificationsEnabled: params.notificationsEnabled,
                    geofencing: params.geofencing ?? false,
                    enableRemoteWork: params.enableRemoteWork ?? false,
                    saveRemoteWorkLocation: (params.enableRemoteWork ?? false) && (params.saveRemoteWorkLocation ?? false),
                },
                users,
                fromDate,
                allUsers,
                policyHourCategories ?? [],
                params.approvalUsers,
            );
            res.json(newPolicy);
            res.status(OK).end();
        });
    };

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

    public assignUsersToPolicy = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, instanceOfLoggedUser, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = res.locals.bodyParams as AssignUserPoliciesVC;
        const date = moment.tz(instanceOfLoggedUser.getTimezone()).format(ISO_DATE);
        let users: number[] = [];
        const allUsers = params.userMode === GROUP_MEMBER_POLICIES.ALL_USERS;
        if (params.userMode === GROUP_MEMBER_POLICIES.TARGETED_USERS) {
            users = params.collaborators!;
        } else if (allUsers) {
            users = await this.usersService.getUserIdsInInstance(loggedUser.instanceId);
        }
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            await this.timeTrackingPoliciesService.associatePolicy(id, users, params.fromDate ?? date, allUsers, loggedUser.instanceId);
            res.status(OK).end();
        });
    };

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

    public getPolicyHourCategories = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id: policyId, loggedUser, instanceOfLoggedUser, requiredLimitOffsetPagination, queryParams } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const { search } = queryParams as GetHourCategoriesVC;
            const result = await this.hourCategorizationService.listHourCategoriesForPolicy(
                instanceOfLoggedUser.getId(),
                policyId,
                requiredLimitOffsetPagination,
                search,
            );
            res.json(
                new TimeTrackingPolicyHourCategoriesSC().serializePaginatedList({
                    count: result.count,
                    limit: requiredLimitOffsetPagination.limit,
                    offset: requiredLimitOffsetPagination.offset,
                    rows: result.rows,
                }),
            );
            res.status(OK).end();
        });
    };

    public getGlobalApprovalUsers = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const steps = await this.timeTrackingPoliciesService.getApprovalSteps(loggedUser.instanceId, null);
            res.status(OK).json(new TimeTrackingGlobalPolicyStepApproversSC().serialize(steps));
        });
    };

    public updateGlobalApprovalUsers = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = bodyParams as UpdateTimeTrackingApprovalUsersVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const steps = await this.timeTrackingPoliciesService.updateApprovalSteps(loggedUser.instanceId, params.approvalUsers, null);
            res.status(OK).json(new TimeTrackingGlobalPolicyStepApproversSC().serialize(steps));
        });
    };

    public getPolicyApprovalUsers = 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 steps = await this.timeTrackingPoliciesService.getApprovalSteps(loggedUser.instanceId, id);
            res.status(OK).json(new TimeTrackingGlobalPolicyStepApproversSC().serialize(steps));
        });
    };

    public updatePolicyApprovalUsers = async (_req: Request, res: DeprecatedHumandResponse, next: NextFunction): Promise<void> => {
        const { id, bodyParams, loggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser as UserDAO);
        const params = bodyParams as UpdateTimeTrackingPolicyApprovalUsersVC;
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageTimeTracking(user);
            const steps = await this.timeTrackingPoliciesService.updateApprovalSteps(loggedUser.instanceId, params.approvalUsers, id);
            res.status(OK).json(new TimeTrackingGlobalPolicyStepApproversSC().serialize(steps));
        });
    };
}
