const query = require("../SDK/db");
const fs = require("fs");
const Url = require("url");
const { v4: uuidv4 } = require('uuid');
// const redisService = require("./redis.js").RedisService;
const { writeModel } = require("../SDK/esSDK/writeModel");
const { readModel } = require("../SDK/esSDK/readModel");
const readEventStore = new readModel();
const writeEventStore = new writeModel();

const redisService = require("../SDK/redisService").RedisService;
const axios = require("axios");
const sleep = (waitTimeInMs) =>
  new Promise((resolve) => setTimeout(resolve, waitTimeInMs));

const sixHoursSeconds = 6 * 60 * 60;
const oneWeekSeconds = 6 * 24 * 60 * 60;

class ResourceService {
  constructor() { }

  /**获取Token */
  async getToken(sessionId, type) {
    var tokenObj = JSON.parse(
      await redisService.get(type + "Token_" + sessionId)
    );
    if (!tokenObj) throw new Error("resource is disabled");
    return tokenObj;
  }

  /**生成随机 id */
  generateId(prefix) {
    return (
      prefix + new Date().getTime() + Math.random().toString(36).substring(2)
    );
  }

  /**根据 options 为每一个会话分配一个 nlu 资源  */
  async getBestAppResource(params, options = { type: "", mode: "", sp: "" }) {
    const sessionId = params.sessionId;
    const appKey = params.appKey;

    let sql = `select * from app where appKey = '${appKey}' limit 1`;
    let apps = await query(sql);
    if (apps.length === 0)
      throw new Error(`[getBestAppResource] appKey ${appKey} not found.` + sql);
    let app = apps[0];

    sql = `select * from \`app_resource\` where qpsCurrent < qpsLimit and state = 'on' and \`group\`= '${app.group}'  and \`type\`= '${options.type}' and \`mode\`= '${options.mode}' and \`sp\`= '${options.sp}'  order by rand()`;
    let resources = await query(sql);
    if (resources.length === 0)
      throw new Error(
        `[getBestAppResource] not found available resource.` + sql
      );
    let resource = resources[0];
    //数据生成
    if (options.sp == "unit_public_v3")
      resource.token = await this.genToken(
        resource.appId,
        resource.secretKey,
        options.sp
      );
    resource.appKey = appKey;

    await this.setResource(sessionId, resource, options);

    sql = `update \`app_resource\` set qpsCurrent = ${resource.qpsCurrent + 1
      } where id = ${resource.id}`;
    await query(sql);
    setTimeout(async () => {
      await this.releaseAppResource(resource);
    }, (sixHoursSeconds - 100) * 1000);
  }

  /**get token by appId and secretKey */
  async genToken(appId, secretKey, sp) {
    const key = "DM_TOKEN_" + appId;
    let token = await redisService.get(key);
    if (token) return token;
    if (sp == "unit_public_v3") {
      const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${appId}&client_secret=${secretKey}`;
      const response = await axios.get(url);
      token = response.data.access_token;

      await redisService.set(key, token, 10 * 24 * 60 * 60);
    }

    return token;
  }
  /**
   * 释放 app resource 资源，请注意这里是主动释放，
   */
  async releaseAppResource(resource) {
    let sql = `update \`app_resource\` set qpsCurrent = ${resource.qpsCurrent} where id = ${resource.id}`;
    await query(sql);
  }

  async releaseResource(sessionId, options = { type: "", mode: "", sp: "" }) {
    //todo: 释放资源
    const key = options.type + `Token_${sessionId}`;
    await redisService.del(key);
  }
  /**write the available resource into redis */
  async setResource(sessionId, resource, options) {
    delete resource["unitAccount"];
    const key = options.type + `Token_${sessionId}`;
    await redisService.set(
      key,
      { ...resource, sessionId, timestamp: new Date() },
      sixHoursSeconds
    );
  }

  async setResourceObj(resource, type, instanceId) {
    const key = type + `Obj_${instanceId}`;
    await redisService.set(
      key,
      { ...resource, timestamp: new Date() },
      sixHoursSeconds * 4 * 7
    );
    //dm初始化
    if (type == "DM") {
      this.initSession("DM_", resource.sessionId, resource.appKey, instanceId);
    }
  }

  async getResourceObj(type, instanceId) {
    const key = type + `Obj_${instanceId}`; 
    var serviceObj = await redisService.get(key);
    return JSON.parse(serviceObj);
  }

  async setResourceState(instanceId, resource, state) { }

  async getBotByBotKey(botKey, dmId, resourceId) {
    let sql = `SELECT * FROM bot  left join supplier on bot.supplierId = supplier.supplierId 
    left join \`real_bot\` realBot on bot.botId = realBot.botId
    left join \`app_resource\` appResource on realBot.resourceId = ${resourceId}
    where botKey = '${botKey}' `;
    let results = await query(sql);
    if (results.length === 0) throw `getBotByBotKey botKey ${botKey} not found.`;
    return results[0];
  }


  async initSession(prefix, sessionId, appKey, dmId) {
    const streamName = prefix + dmId;
    const streamExist = await this.checkStreamExist(streamName);
    if (streamExist) throw new Error(`Stream ${streamName} already exists in the ES-DB.`);

    const appInfo = await this.getInfoByAppId(appKey);
    delete appInfo['id'];

    const eventData = {
      id: uuidv4(),
      sessionId: sessionId,
      data: {
        eventKey: 'init',
        ...appInfo,
        dmId
      }
    }
    await writeEventStore.appendToStream(streamName, "init", eventData);
    redisService.set(prefix + dmId, {
      dm: { sessionId, dmInfo: eventData.data }
    }, oneWeekSeconds);
  }

  //存在：true 不存在：false
  async checkStreamExist(streamName) {
    const result = await readEventStore.readStream(streamName, { maxCount: 1 });
    return result.code === 200;
  }

  /**根据 appKey 获取相关信息 */
  async getInfoByAppId(appKey) {
    const sql = `select * from app where appKey = '${appKey}' limit 1`;
    const results = await query(sql);
    if (results.length === 0) throw new Error(`getInfoByAppId appKey ${appKey} does not exist.`);

    return results[0];
  }
}

exports.ResourceService = ResourceService;
