/**
 * redis模块
 * @date 2022-03
 * @author 何华东
 */

import { logger } from "mslib";
import { IocConfigProvider, Config } from "./Config";
import { ConfigCenterConfigProvider } from "./ConfigCenter";
import { IS_DEBUG, IS_PARK, IS_PROD } from "./Contants";
import { throttle1 } from "./Throttle";
import { init as init_i18n } from "./Global";
import { uuid } from "./Utils";
const IORedis = require("ioredis");
import DateUtils from "./Date";

export type RedisClient = {
    get: Function;
    set: Function;
    del: Function;

    hget: Function;
    hset: Function;
    hgetall: Function;

    [cmd: string]: Function
}

/**
 * redis客户端实例
 * 
 * 1.`Ioc`注册`ioredis`配置将会自动创建该实例; @see {@link Config}
 * 
 * 2.如已引用配置依赖[NsConfig](https://me.keytop.cn/service/config)将会自动创建该实例;
 * 
 * @see [ioredis](https://www.npmjs.com/package/ioredis)
 */
export let redisClient: RedisClient = null;

init();

/**
 * 场端从sqlite获取redis密码
 */
async function getRedisPwdFromSqlite() {
    let pwd;

    const knexInstance = require('knex')({
        client: 'sqlite3',
        connection: {
            filename: "/home/keytop/data/lotconfig.db"
        }
    });

    let rows = await knexInstance('t_config').where({ session: 'redis', name: 'password' });
    if (rows && rows.length > 0 && rows[0].value) pwd = rows[0]?.value;

    return pwd;
}

async function init() {
    try {
        let config = IocConfigProvider.getIORedisConfig() || await ConfigCenterConfigProvider.getRedisConfig();
        if (!config) return;

        //场端环境&redis未配置密码，则自动从sqlite获取redis密码
        if (IS_PARK && !config.password) {
            try {
                config.password = await getRedisPwdFromSqlite();
            } catch (error) {
                logger.error('从sqlite获取redis密码失败', error);
            }
        }

        let logFunc = throttle1((error) => { logger.error(error); });
        //默认懒连接
        redisClient = new IORedis(Object.assign({ lazyConnect: true }, config));

        redisClient.on("error", error => {
            if (['ETIMEDOUT', 'ECONNREFUSED'].includes(error.code)) {
                logFunc(error);
            } else {
                logger.error(error);
            }
        });

        logger.info(`redis客户端实例创建成功,host:${config.host || 'localhost'}`);

        //初始化i18n
        init_i18n();
    } catch (error) {
        logger.error('redis客户端实例创建失败', error);
    }
}


/**
 * redis分布式锁装饰器
 * 
 * **注：如果加锁失败则不会执行被装饰的函数,而是执行lockFailedHandler**
 * 
 * ```ts
 * import { RedisLocker } from "refs/nslib";
 * 
 * //去掉注释
 * //@RedisLocker(args => { return `${args.parkId}.TASK`; }, args => { return '任务已在执行,请稍后再试'; })
 * async function exec(){
 * 
 * }
 * ```
 * @param key redis锁的key
 * @param lockFailedHandler 加锁失败处理
 */
export function RedisLocker(key: ((...args) => string) | string, lockFailedHandler: (...args) => any, opt?: {
    /**
     * 过期时间(秒)
     */
    ex: number,

    /**
     * 是否自动续约,自动续约时间间隔间隔为过期时间的一半,且最低5秒
     */
    autoRenew?: boolean,
}): MethodDecorator {
    return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;

        descriptor.value = async function (...args) {
            let _key = typeof key === 'function' ? key(args) : key;
            let value = DateUtils.getUnixTimestamp().toString();
            let _timer = null;
            let res = null;
            
            if (opt?.ex) {
                res = await redisClient.set(_key, uuid(), 'NX', 'EX', opt.ex);
            } else { 
                res = await redisClient.set(_key, value, 'NX');
            }
            
            //加锁失败
            if (res !== 'OK') {
                logger.warn(`redis 加锁失败,key:${_key},value:${value}`);
                return lockFailedHandler(args);
            }
            else {
                logger.info(`redis 加锁成功,key:${_key}`);
            }

            //自动续约
            if (opt?.autoRenew) {
                _timer = setInterval(async () => {
                    try {
                        let res = await redisClient.expire(_key, opt.ex);
                        if (res !== 1) {
                            logger.warn(`redis 锁自动续约失败,key:${_key}`);
                            clearInterval(_timer);
                        }

                        console.log(`redis 锁自动续约成功,key:${_key}`);
                    } catch (error) {
                        logger.error(`redis 锁自动续约失败,key:${_key}`, error);
                    }
                },  opt.ex / 2 * 1000);
            }

            try {
                return await method.apply(this, args);
            } finally {
                if (_timer) clearInterval(_timer);
                let res = await redisClient.del(_key);
                logger.info(`redis 释放锁,key:${_key},res:${res}`);
            }
        }
    }
}