import {env} from "../../index";
import API from "../api/service.api";
import {forwardRef, Inject, Injectable, OnModuleInit, RequestMethod, VersioningOptions} from '@nestjs/common';
import {ApplicationConfig, DiscoveryService, MetadataScanner, ModulesContainer, Reflector} from '@nestjs/core';
import {bodyRequestSync} from "../model/requests/body.request.sync";
import {ResponseModel} from "../model/responses/api.response";
import {PATH_METADATA, VERSION_METADATA} from "@nestjs/common/constants";



@Injectable()
export class RouterService {
    constructor(
        @Inject(forwardRef(() => DiscoveryService))
        private readonly discoveryService: DiscoveryService,
        @Inject(Reflector.name)
        private readonly reflector: Reflector,
        @Inject(forwardRef(() => ApplicationConfig))
        private readonly applicationConfig: ApplicationConfig,
        private readonly metadataScanner: MetadataScanner,
        @Inject(forwardRef(() => ModulesContainer))
        private readonly moduleContainer: ModulesContainer,
    ) {
    }

    async syncRouters() {
        try{
            const timeStart = new Date().getTime();
            let arrayModuleRouter: bodyRequestSync[] = [];
            const globalPrefix = this.applicationConfig.getGlobalPrefix().replace(/^\/|\/$/g, '');
            const defaultVersion: VersioningOptions | undefined = this.applicationConfig.getVersioning();
            const modules = [...this.moduleContainer.values()];
            console.info("1-get modules");
            for (const module of modules) {
                let moduleRouter: bodyRequestSync = {
                    pathModule: [],
                    nameModule: '',
                };
                const moduleName = module.metatype.name;
                const controllers = [...module.controllers.values()];
                controllers.forEach((controller) => {
                    try{
                        const { instance } = controller;
                        const prototype = Object.getPrototypeOf(instance);
                        this.metadataScanner.scanFromPrototype(instance, prototype, (methodName) => {
                            const method = prototype[methodName];
                            const apiOperationMetadata = this.reflector.get('swagger/apiOperation', method);
                            const paths = this.reflector.get<string>('path', method);
                            const methodType = this.reflector.get<RequestMethod[]>('method', method);
                            const controllerVersion = this.reflector.get<string>(VERSION_METADATA, prototype.constructor);
                            const controllerPath = this.reflector.get<string>(PATH_METADATA, prototype.constructor);
                            const version = `${this.generateVersion(defaultVersion, controllerVersion) || ''}`;
                            const pathController = controllerPath === '/' ? '' : `/${controllerPath.replace(/^\/|\/$/g, '')}`;
                            const path = paths === '/' ? '' : `/${paths.replace(/^\/|\/$/g, '')}`;
                            const prefix = globalPrefix === undefined ? '' : `${globalPrefix.replace(/^\/|\/$/g, '')}`;
                            const pathMap = `${prefix}${version}${pathController}${path}`.replace(/^\/|\/$/g, '');
                            switch (Number(methodType)) {
                                case 1:
                                    moduleRouter.pathModule.push({
                                        method: methodsEnums.POST.toString(),
                                        path: pathMap,
                                        function: methodName,
                                        description:
                                            apiOperationMetadata === undefined ? '' : apiOperationMetadata['description'],
                                    });
                                    break;
                                case 2:
                                    moduleRouter.pathModule.push({
                                        method: methodsEnums.PUT.toString(),
                                        path: pathMap,
                                        function: methodName,
                                        description:
                                            apiOperationMetadata === undefined ? '' : apiOperationMetadata['description'],
                                    });
                                    break;
                                case 3:
                                    moduleRouter.pathModule.push({
                                        method: methodsEnums.DELETE.toString(),
                                        path: pathMap,
                                        function: methodName,
                                        description:
                                            apiOperationMetadata === undefined ? '' : apiOperationMetadata['description'],
                                    });
                                    break;
                                case 4:
                                    moduleRouter.pathModule.push({
                                        method: methodsEnums.PATCH.toString(),
                                        path: pathMap,
                                        function: methodName,
                                        description:
                                            apiOperationMetadata === undefined ? '' : apiOperationMetadata['description'],
                                    });
                                    break;
                                case 0:
                                    moduleRouter.pathModule.push({
                                        method: methodsEnums.GET.toString(),
                                        path: pathMap,
                                        function: methodName,
                                        description:
                                            apiOperationMetadata === undefined ? '' : apiOperationMetadata['description'],
                                    });
                                    break;
                            }
                        });
                        moduleRouter.nameModule = moduleName.toString();
                    }catch(error) {
                        console.error(error);
                    }
                });
                console.info("2-get done path controller and module ");
                if (moduleRouter.pathModule.length && moduleRouter.nameModule !== '') {
                    console.info("3-push controller and module");
                    arrayModuleRouter.push(moduleRouter);
                }
            }
            await Promise.all(arrayModuleRouter.map(async (r)=>{
                const result = await this.syncRouter([r]);
                console.info(`4-result push controller: ${result} using time : ${(new Date().getTime() - timeStart)}`);
            }))


        }catch (e){
            console.error(`[ERROR]: cms-package ${e}`);
        }
    }

    private async syncRouter(info: bodyRequestSync[]): Promise<boolean> {
        try{
            const result = await API.postApi(env.SYNC_URL, info, '', env.KEY_SERVICE) as ResponseModel;
            return result.data;
        }catch (e){
            console.log(`[Error] sync log : ${e}`);
            return false;
        }

    }

    private generateVersion(defaultVersion: any, versionController: any): string| undefined {
        if (!versionController && !defaultVersion) {
            return '';
        } else if ((versionController && !defaultVersion) || (versionController && defaultVersion)) {
            return `/v${versionController?.toString()}`.toString();
        } else if (!versionController && defaultVersion) {
            return `/v${defaultVersion['defaultVersion']?.toString()}`.toString();
        }
    }
}
