import { NextFunction } from 'express';
import { LoggerModules } from '../../business/constants/loggerModules';
import { mimeExtensions, reportExtensionToMimeType } from '../../business/constants/mimeTypes';
import { BadRequestError, DatabaseError } from '../../business/errors';
import { ErrorCodes } from '../../business/errors/errorCodes';
import { ReportFile, Xlsx } from '../../business/interfaces/reports';
import { LoggerPort } from '../../business/ports/loggerPort';
import { sanitizeFileName } from '../../infrastructure/helpers/strings';
import { sleep } from '../../infrastructure/helpers/util';
import { HumandResponse } from '../types/response';

export class BaseController<NotificationParams = Record<string, unknown>> {
    constructor(
        protected readonly logger: LoggerPort,
        private readonly notificationDelay?: number,
    ) {}

    private baseEndpointHandler = async (
        next: NextFunction,
        logicFn: () => NotificationParams | void | Promise<NotificationParams | void>,
        notificationFn?: (notificationParams: NotificationParams | void) => void | Promise<void>,
        logicLogger: (error: any) => void = () => undefined,
    ): Promise<void> => {
        let notificationParams: NotificationParams | void = undefined;
        try {
            notificationParams = await logicFn();
        } catch (error) {
            logicLogger(error);
            return next(error);
        }
        if (notificationFn) {
            if (this.notificationDelay) await sleep(this.notificationDelay);
            try {
                await notificationFn(notificationParams);
            } catch (error) {
                this.logger.error(error);
            }
        }
    };

    /**
     * @deprecated this method should not be used.
     * Use endpointHandlerWithGenerics with a specific type for notification params.
     * Eventually, delete this method and rename endpointHandlerWithGenerics to endpointHandler.
     */
    protected endpointHandler = async (
        next: NextFunction,
        logicFn: () => NotificationParams | void | Promise<NotificationParams | void>,
        notificationFn?: (notificationParams: NotificationParams | void) => void | Promise<void>,
    ): Promise<void> => {
        await this.baseEndpointHandler(next, logicFn, notificationFn);
    };

    /**
     * @deprecated this method should not be used.
     * Use devopsEndpointHandlerWithGenerics with a specific type for notification params.
     * Eventually, delete this method and rename devopsEndpointHandlerWithGenerics to devopsEndpointHandler.
     */
    protected devopsEndpointHandler = async (
        next: NextFunction,
        logicFn: () => NotificationParams | void | Promise<NotificationParams | void>,
        notificationFn?: (notificationParams: NotificationParams | void) => void | Promise<void>,
    ): Promise<void> => {
        await this.baseEndpointHandler(next, logicFn, notificationFn, (error) => this.logger.child(LoggerModules.DEVOPS).error(error));
    };

    protected baseEndpointHandlerWithGenerics = async <EndpointNotificationParams>(
        next: NextFunction,
        logicFn: () => EndpointNotificationParams | Promise<EndpointNotificationParams>,
        notificationFn?: (notificationParams: EndpointNotificationParams) => void | Promise<void>,
        logicLogger: (error: any) => void = () => undefined,
    ): Promise<void> => {
        let notificationParams: EndpointNotificationParams | void = undefined;
        try {
            notificationParams = await logicFn();
        } catch (error) {
            logicLogger(error);
            if (DatabaseError.isUniqueConstraintError(error)) {
                return next(new BadRequestError('entity already exist', ErrorCodes.ENTITY_ALREADY_EXIST));
            }
            return next(error);
        }
        if (notificationFn) {
            if (this.notificationDelay) await sleep(this.notificationDelay);
            try {
                await notificationFn(notificationParams);
            } catch (error) {
                this.logger.error(error);
            }
        }
    };

    protected endpointHandlerWithGenerics = async <EndpointNotificationParams extends Record<string, unknown> | void = void>(
        next: NextFunction,
        logicFn: () => EndpointNotificationParams | Promise<EndpointNotificationParams>,
        notificationFn?: (notificationParams: EndpointNotificationParams) => void | Promise<void>,
    ): Promise<void> => {
        await this.baseEndpointHandlerWithGenerics<EndpointNotificationParams>(next, logicFn, notificationFn);
    };

    protected devopsEndpointHandlerWithGenerics = async <EndpointNotificationParams extends Record<string, unknown> | void = void>(
        next: NextFunction,
        logicFn: () => EndpointNotificationParams | Promise<EndpointNotificationParams>,
        notificationFn?: (notificationParams: EndpointNotificationParams) => void | Promise<void>,
    ): Promise<void> => {
        await this.baseEndpointHandlerWithGenerics<EndpointNotificationParams>(next, logicFn, notificationFn, (error) =>
            this.logger.child(LoggerModules.DEVOPS).error(error),
        );
    };

    protected writeExcelReportToRes = async (res: HumandResponse, reportXlsxFile: ReportFile<Xlsx>, mimeExtension = mimeExtensions.xlsx) => {
        const mimeType = reportExtensionToMimeType[mimeExtension];
        res.setHeader('Content-Type', mimeType);
        res.setHeader('Content-Disposition', `attachment; filename="${sanitizeFileName(reportXlsxFile.filename)}"`);
        await reportXlsxFile.file.write(res);
    };
}
