import { BadRequestError, BaseController, CapabilityName, ErrorCodes, LoggerPort } from '@humand-packages/common';
import { NextFunction, Request } from 'express';
import { CREATED, NO_CONTENT, OK } from 'http-status-codes';
import moment from 'moment';
import { Service } from 'typedi';
import { PermissionsService } from '../../../permissions/business/services/permissionsService';
import { RegionsService } from '../../../regions/business/services/regionService';
import { userDAOtoUser } from '../../../users/infrastructure/mappers/usersMapper';
import { PublicUserSerializer } from '../../../users/presentation/serializationClasses/publicUserSerializer';
import ShiftAssignedDayService from '../../business/services/shiftManagement/shiftAssignedDay';
import ShiftAssignmentService from '../../business/services/shiftManagement/shiftAssignment';
import ShiftCalendarService from '../../business/services/shiftManagement/shiftCalendar';
import { ShiftService } from '../../business/services/shiftManagement/shiftService';
import { PublicApiAssignedShiftSC } from '../serializationClasses/PublicApiAssignedShiftSC';
import { PublicApiShiftTemplateSC } from '../serializationClasses/PublicApiShiftTemplateSC';
import { ShiftRegionSC } from '../serializationClasses/ShiftRegionSC';
import { ShiftsSC } from '../serializationClasses/ShiftsSC';
import { ShiftsResponse } from '../types/response';
import { DuplicateShiftPeriodVC } from '../validationClasses/shiftManagement/duplicateShiftPeriodVC';
import { GetPublicCalendarAPIRequestVC } from '../validationClasses/shiftManagement/getPublicCalendarAPIRequestVC';
import { GetRegionsVC } from '../validationClasses/shiftManagement/getRegionsVC';
import { ShiftAssignBulkRequestVC } from '../validationClasses/shiftManagement/shiftAssignBulkRequestVC';
import { ShiftAssignRequestVC } from '../validationClasses/shiftManagement/shiftAssignVC';
import { ShiftBulkDeleteVC } from '../validationClasses/shiftManagement/shiftBulkDeleteVC';
import { ShiftCopyAndPasteVC } from '../validationClasses/shiftManagement/shiftCopyAndPasteVC';
import { ShiftManagementCalendarFiltersVC } from '../validationClasses/shiftManagement/shiftManagementCalendarFiltersVC';
import { ShiftManagementUsersFiltersVC } from '../validationClasses/shiftManagement/shiftManagementUsersFiltersVC';
import { ShiftMoveVC } from '../validationClasses/shiftManagement/shiftMoveVC';
import { ShiftMyCalendarVC } from '../validationClasses/shiftManagement/shiftMyCalendar';
import { ShiftsListFiltersVC } from '../validationClasses/shiftManagement/shiftsListFiltersVC';
import { ShiftTemplateVC } from '../validationClasses/shiftManagement/shiftTemplateVC';

const MAX_DAYS_BETWEEN_DATES = 31;

@Service()
export class ShiftManagementController extends BaseController {
    constructor(
        private readonly shiftAssignedDayService: ShiftAssignedDayService,
        private readonly shiftService: ShiftService,
        private readonly shiftCalendarService: ShiftCalendarService,
        private readonly shiftAssignmentService: ShiftAssignmentService,
        private readonly permissionService: PermissionsService,
        private readonly regionsService: RegionsService,
        logger: LoggerPort,
    ) {
        super(logger);
    }

    public getShifts = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { loggedUser, instanceOfLoggedUser, queryParams, requiredLimitOffsetPagination: pagination } = res.locals;
        const filters = queryParams as ShiftsListFiltersVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const result = await this.shiftService.getShifts(instanceOfLoggedUser.getId(), filters, pagination);
            res.json(
                new ShiftsSC().serializePaginatedList({
                    count: result.count,
                    limit: pagination.limit,
                    offset: pagination.offset,
                    rows: result.rows,
                }),
            );
            res.status(OK).end();
        });
    };

    public deleteAssignedShift = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { shiftAssignedDayId, shiftAssignedShiftId, loggedUser, instanceOfLoggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageShiftsCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            await this.shiftAssignmentService.deleteShiftAssignedShift(
                shiftAssignedShiftId,
                shiftAssignedDayId,
                instanceOfLoggedUser.getId(),
                loggedUser.getId(),
                hasManageShiftsCapability,
            );
            res.status(OK).end();
        });
    };

    public bulkProcessShifts = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftAssignBulkRequestVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            const shifts = await this.shiftAssignedDayService.bulkProcessShiftsApi(instanceOfLoggedUser.getId(), params);
            res.json(shifts);
            res.status(OK).end();
        });
    };

    public calendar = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { queryParams, loggedUser, instanceOfLoggedUser, requiredLimitOffsetPagination: pagination } = res.locals;
        const params = queryParams as ShiftManagementCalendarFiltersVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            const calendar = await this.shiftCalendarService.calendar(instanceOfLoggedUser, user, params, hasManageCapability, pagination);
            res.json(calendar);
            res.status(OK).end();
        });
    };

    public myCalendar = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { queryParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = queryParams as ShiftMyCalendarVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            this.validateDates(params);
            const calendar = await this.shiftCalendarService.myCalendar(instanceOfLoggedUser, user.getId(), params);
            res.json(calendar);
            res.status(OK).end();
        });
    };

    public assign = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftAssignRequestVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            const shift = await this.shiftAssignmentService.assign(instanceOfLoggedUser, user, params, hasManageCapability);
            res.json(shift);
            res.status(shift ? CREATED : OK).end();
        });
    };

    public getRegions = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { queryParams, loggedUser, instanceOfLoggedUser, requiredLimitOffsetPagination: pagination } = res.locals;
        const params = queryParams as GetRegionsVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const countedRegions = await this.regionsService.findAll(instanceOfLoggedUser.getId(), pagination, true, params.search);
            res.status(OK).json(
                new ShiftRegionSC().serializePaginatedList({
                    count: countedRegions.totalCount,
                    limit: pagination.limit,
                    offset: pagination.offset,
                    rows: countedRegions.regions,
                }),
            );
        });
    };

    public listUsers = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { limitOffsetPagination, sortingOptions, loggedUser, queryParams } = res.locals;
        const params = queryParams as ShiftManagementUsersFiltersVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            const { rows, count } = await this.shiftService.listUsers(user, hasManageCapability, params, {
                pagination: limitOffsetPagination,
                sortingOptions,
            });
            res.status(OK).json(
                new PublicUserSerializer().serializePaginatedList(rows, limitOffsetPagination.limit!, limitOffsetPagination.offset!, count),
            );
        });
    };

    public bulkDelete = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftBulkDeleteVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            await this.shiftAssignmentService.bulkDelete(instanceOfLoggedUser, user, params, hasManageCapability);
            res.status(OK).end();
        });
    };

    public copyAndPaste = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftCopyAndPasteVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            await this.shiftAssignmentService.copyAndPaste(instanceOfLoggedUser, user, params, hasManageCapability);
            res.status(OK).end();
        });
    };

    public move = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftMoveVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            await this.shiftAssignmentService.move(instanceOfLoggedUser, user, params, hasManageCapability);
            res.status(OK).end();
        });
    };

    public duplicateShiftPeriod = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as DuplicateShiftPeriodVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateViewShifts(user);
            const hasManageCapability = this.permissionService.hasAnyCapability(user, [CapabilityName.MANAGE_SHIFTS]);
            await this.shiftAssignmentService.duplicateShiftPeriod(instanceOfLoggedUser, user, params, hasManageCapability);
            res.status(OK).end();
        });
    };

    public createShiftTemplate = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser } = res.locals;
        const params = bodyParams as ShiftTemplateVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            const isTemplate = true;
            const shiftTemplate = await this.shiftService.createShift(instanceOfLoggedUser.getId(), isTemplate, params);
            res.json(shiftTemplate);
            res.status(CREATED).end();
        });
    };

    public updateShiftTemplate = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { bodyParams, loggedUser, instanceOfLoggedUser, id } = res.locals;
        const params = bodyParams as ShiftTemplateVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            const shiftTemplate = await this.shiftService.updateShiftTemplate(instanceOfLoggedUser.getId(), params, id);
            res.json(shiftTemplate);
            res.status(OK).end();
        });
    };

    public deleteShiftTemplate = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { id, loggedUser, instanceOfLoggedUser } = res.locals;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            await this.shiftService.removeFromTemplates(instanceOfLoggedUser.getId(), id);
            res.status(NO_CONTENT).end();
        });
    };

    public getPublicCalendar = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { queryParams, loggedUser, instanceOfLoggedUser, requiredLimitOffsetPagination: pagination } = res.locals;
        const params = queryParams as GetPublicCalendarAPIRequestVC;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            const result = await this.shiftCalendarService.publicApiCalendar(
                instanceOfLoggedUser,
                params.employeeId,
                pagination,
                params.dateFrom,
                params.dateTo,
            );
            res.status(OK).json(
                new PublicApiAssignedShiftSC().serializePaginatedList({
                    count: result.count,
                    limit: pagination.limit,
                    offset: pagination.offset,
                    rows: result.rows,
                }),
            );
        });
    };

    public listShiftTemplates = async (_req: Request, res: ShiftsResponse, next: NextFunction): Promise<void> => {
        const { loggedUser, instanceOfLoggedUser, requiredLimitOffsetPagination: pagination } = res.locals;
        const user = userDAOtoUser(loggedUser);
        await this.endpointHandlerWithGenerics(next, async () => {
            this.permissionService.validateManageShifts(user);
            const result = await this.shiftService.getShiftTemplates(instanceOfLoggedUser.getId(), pagination);
            res.status(OK).json(
                new PublicApiShiftTemplateSC().serializePaginatedList({
                    count: result.count,
                    limit: pagination.limit,
                    offset: pagination.offset,
                    rows: result.rows,
                }),
            );
        });
    };

    private validateDates(params: ShiftMyCalendarVC): void {
        if (moment(params.endDate).diff(moment(params.startDate), 'days') > MAX_DAYS_BETWEEN_DATES) {
            throw new BadRequestError(
                `Max diff between start and end date is ${MAX_DAYS_BETWEEN_DATES} days`,
                ErrorCodes.SHIFT_MANAGEMENT_INVALID_PARAMS,
            );
        }
        if (moment(params.endDate).isBefore(moment(params.startDate))) {
            throw new BadRequestError('End date must be after start date', ErrorCodes.SHIFT_MANAGEMENT_INVALID_PARAMS);
        }
    }
}
