import 'reflect-metadata';
require('dotenv').config();
import 'zone.js';
import {v4} from 'uuid'
import * as fastify from 'fastify'
//import * as fastifyCors from 'fastify-cors'
import {init} from "./init/initScript";
import {ServiceRouters} from "./routers/ServiceRouters";
import {MeHeader} from "./utils/MeHeader";
import {HeartbeatTask} from "./heartbeat/HeartbeatTask";
import {Context, EnvVariables, logger, RedisTokenRepository} from "mslib"
import {IServiceProxy} from './proxy/IServiceProxy';
import {WsServiceProxy} from './proxy/WsServiceProxy';
import {ConfigLoader} from './utils/ConfigLoader'
import {NodeHttpPortUtil} from './utils/NodeHttpPortUtil'
import {DebugContext} from './utils/DebugContext'
import {UTSJobScheduler} from './uts/UTSJobScheduler'
import {HandlerExecutior} from './interceptor/HandlerExecutior'
import {Container} from 'typescript-ioc';
import * as Debugger from 'debugger';
import * as promClient from 'prom-client';
import * as FastifyWebsocket from 'fastify-websocket';
import {EventHandlerEntrance} from "./event/EventHandlerEntrance";
import {ServiceScanner} from "./routers/ServiceScanner";
import * as path from "path";

promClient.collectDefaultMetrics();

require('json-date-util').overrideParser();

let httpPort = NodeHttpPortUtil.getHttpPort();

const debugContext: DebugContext = {
    initData: undefined,
    globalZone: undefined,
    initialized: false
};


const webApp = fastify.fastify({bodyLimit: 1024 * 1024 * 1024, keepAliveTimeout:60000,caseSensitive:false})

//跨域处理
//webApp.register(fastifyCors)
//文件上传支持
const options = {
    addToBody: true
}
webApp.register(require('fastify-multipart'), options)
webApp.register(require('fastify-formbody'))
webApp.register(FastifyWebsocket.default)
webApp.register(require('fastify-raw-body'), {
    field: 'rawBody'
});
//Fastify仅支持'application/json'和'text/plain'内容类型。默认字符集为utf-8
webApp.addContentTypeParser('*', (req, done) => {
    let body = ''
    req.on('error', errorListener)
    req.on('data', dataListener)
    req.on('end', endListener)

    function errorListener(err) {
        done(err)
    }

    function endListener() {
        done(null, body)
    }

    function dataListener(data) {
        body = body + data
    }
})

let contextPath = EnvVariables.getNodeHttpContextPath();
if (Debugger.isDebug) { //开发环境调试
    contextPath = Debugger.getContextPath();
    httpPort = Debugger.getPort()
}
logger.info('isDebug:' + Debugger.isDebug + ' ,port:' + httpPort + 'contextPath:' + contextPath);

let inited = false;
let globalZone: Zone;

const serviceRouter = new ServiceRouters();
const wsServiceProxy = new WsServiceProxy(serviceRouter)

webApp.register(async function (instance: fastify.FastifyInstance, opts, done) {

        //查询服务状态
        instance.get("/status", async function (request, reply) {
            const obj = {msg: "node is running....", port: httpPort};
            reply.code(200).send(obj)
        });

        //prometheus客户端
        instance.get("/metrics", async function (request, reply) {
            try {
                reply.code(200).send(await promClient.register.metrics())
            } catch (ex) {
                reply.code(200).send(ex);
            }
        });

        instance.all("/", preHandleService);

        //模板占位符，不可删除
        //##dispatchedByUri##

        // 动态URI路由 - 支持通过URL路径访问服务
        instance.all("/:serviceName", async function (request, reply) {
            const serviceName = (request.params as any).serviceName;
            if (!serviceName || serviceName === 'status' || serviceName === 'metrics') {
                return;
            }
            // 设置sid到header中
            (request.headers as any).sid = serviceName;
            // 清除params避免serviceName混入业务参数
            (request as any).params = {};
            return preHandleService(request, reply);
        });


        //开发环境调试
        if (Debugger.isDebug) {
            await Debugger.registerDebugger(instance, debugContext)
        }

        //完成
        done();
    },
    {prefix: "/" + contextPath}
)

/**
 * 微服务接口调用响应
 * @param serviceRouter
 * @param meHeader
 * @param ctx
 */
async function preHandleService(request, reply) {
    if (!debugContext.initialized) {
        reply.code(500).send('Not inited!')
        return;
    }
    globalZone = debugContext.globalZone;
    try {
        const result = await executeInterceptor(request, reply)
        if (!result) {
            return
        }
        await handleService(request, reply);
    } catch (error) {
        reply.code(500).send(error.toString());
        logger.error(error)
    }
}

/**
 * 微服务接口调用响应
 * @param serviceRouter
 * @param meHeader
 * @param ctx
 */
async function handleService(request, reply) {
    return new Promise((resolver, reject) => {
        try {
            const meHeader = MeHeader.parse(request.headers);
            let doServiceLog = "doService ---MeHeader:" + meHeader.toString()
            logger.info(doServiceLog);
            const userInfo = MeHeader.parseUserInfo(meHeader)
            const service: IServiceProxy = serviceRouter.match(meHeader.sid);
            if (!service || service == null) {
                let errorInfo = "[" + meHeader.sid + "] Service Not found!";
                reply.code(404).send(errorInfo);
                logger.trace(doServiceLog + ' response:' + errorInfo)
                logger.error(errorInfo);
                reject(errorInfo);
                return;
            }
            const contextProperties = {}
            contextProperties[Context.USER_INFO_PROP_NAME] = userInfo;
            contextProperties[Context.RESPONSE_HEADER_PROP_NAME] = reply;
            contextProperties[Context.REQUEST_RAW_BODY_PROP_NAME] = request.rawBody;
            const headers = request.headers || {};
            if(!headers['trace-id']){
                headers['trace-id'] = v4()
            }
            contextProperties[Context.REQUEST_HEADER_PROP_NAME] = headers;

            let currentZone = globalZone.fork({name: v4(), properties: contextProperties});
            currentZone.run(async () => {
                try {
                    let result = await service.doService(request);
                    if(service.getContentType()){
                        reply.type(service.getContentType()).code(200).send(result);
                    }else{
                        reply.code(200).send(result);
                    }
                    resolver();
                } catch (error) {
                    processServiceError(reply, error, reject);
                }
            })
        } catch (error) {
            processServiceError(reply, error, reject);
        }
    })
}

function processServiceError(reply, error: any, reject: any) {
    reply.code(500).send(error.toString());
    logger.error(error);
    reject(error);
}



/**
 * 执行拦截器
 * @param request
 * @param reply
 */
async function executeInterceptor(request, reply): Promise<boolean> {
    const handlerExecutior: HandlerExecutior = Container.get(HandlerExecutior)
    if (handlerExecutior) {
        return await handlerExecutior.preService(request, reply)
    } else {
        return true;
    }
}

async function initConfig() {
    const config = await ConfigLoader.load();
    if (config && config['auth.mode'] === 'NODE') {
        Container.get(RedisTokenRepository).init()
    }
    if (!Debugger.isDebug) {
        const initData = await init();
        debugContext.globalZone = globalZone = Zone.current.fork({name: v4(), properties: initData || {}});
        debugContext.initialized = inited = true;
    }
    ServiceScanner.scanAndRegister(__dirname);
    serviceRouter.refreshRouters();
}


webApp.listen(httpPort, '0.0.0.0',async (err, address) => {
    if (err) throw err

    await initConfig();
    await Debugger.dmOnReady();
    logger.info(`server listening on ${address}`);
    Container.get(UTSJobScheduler).start();
    Container.get(HeartbeatTask).start();
    Container.get(EventHandlerEntrance).startConsume();
});



