const { fork } = require('child_process');

const childOptions = process.env.NODE_ENV == 'development' ? {
  execArgv: ['--inspect=9228']
} : {};
const events_child = fork('./triangle/eventsMatch.js', [], childOptions);

const Logs = require('../libs/logs');
const Relation = require('./Relation');

const Request = {
  callbacks: {},
  count: 0,
}

const GAMES = {
  List: {},
  Relations: {},
  Solutions: {},
};

/**
 * 精确浮点数字
 * @param {number} number
 * @param {number} x
 * @returns {number}
 */
const fixFloat = (number, x=2) => {
  return parseFloat(number.toFixed(x));
}

/**
 * 更新比赛列表
 */
const updateGamesList = (({ platform, mk, games } = {}) => {
  return new Promise((resolve, reject) => {
    if (!platform || !games) {
      return reject(new Error('PLATFORM_GAMES_INVALID'));
    }

    const marketType = mk == 0 ? 'early' : 'today';

    let gamesList = games;

    if (platform == 'jc') {
      gamesList = [];

      const gamesEvents = [];
      const gamesOutrights = [];

      games.forEach(game => {
        const { eventId, events, evtime, special, sptime, ...gameInfo } = game;
        gamesList.push({ eventId, ...gameInfo });
        gamesEvents.push({ eventId, events, evtime });
        gamesOutrights.push({ parentId: eventId, special, sptime });
      });

      updateGamesEvents({ platform, games: gamesEvents, outrights: gamesOutrights });
    }


    const timestamp = Date.now();

    const GAMES_LIST = GAMES.List;

    if (!GAMES_LIST[platform]) {
      GAMES_LIST[platform] = {};
    }
    if (!GAMES_LIST[platform][marketType]) {
      GAMES_LIST[platform][marketType] = { games: gamesList, timestamp };
      return resolve({ add: gamesList.length, del: 0 });
    }

    const oldGames = GAMES_LIST[platform][marketType].games;
    const newGames = gamesList;

    const updateCount = {
      add: 0,
      del: 0,
    };

    const newMap = new Map(newGames.map(item => [item.eventId, item]));
    for (let i = oldGames.length - 1; i >= 0; i--) {
      if (!newMap.has(oldGames[i].eventId)) {
        oldGames.splice(i, 1);
        updateCount.del += 1;
      }
    }

    const oldIds = new Set(oldGames.map(item => item.eventId));
    const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});
    const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
    newGames.forEach(item => {
      if (!oldIds.has(item.eventId)) {
        oldGames.push(item);
        updateCount.add += 1;
      }
      if (relatedMap.has(item.eventId)) {
        const relatedGame = relatedMap.get(item.eventId);
        relatedGame.mk = mk;
      }
    });

    GAMES_LIST[platform][marketType].timestamp = timestamp;

    resolve(updateCount);
  });
});

/**
 * 更新比赛盘口
 */
const updateGamesEvents = ({ platform, mk, games, outrights }) => {
  return new Promise((resolve, reject) => {
    if (!platform || (!games && !outrights)) {
      return reject(new Error('PLATFORM_GAMES_INVALID'));
    }

    const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});

    if (!relatedGames.length) {
      return resolve({ update: 0 });
    }

    const updateCount = {
      update: 0
    };

    const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));

    games?.forEach(game => {
      const { eventId, evtime, events } = game;
      const relatedGame = relatedMap.get(eventId);
      if (relatedGame) {
        relatedGame.evtime = evtime;
        relatedGame.events = events;
        updateCount.update ++;
      }
    });

    outrights?.forEach(outright => {
      const { parentId, sptime, special } = outright;
      const relatedGame = relatedMap.get(parentId);
      if (relatedGame) {
        relatedGame.sptime = sptime;
        relatedGame.special = special;
        updateCount.update ++;
      }
    });

    resolve(updateCount);
  });
}

/**
 * 获取比赛列表
 */
const getGamesList = () => {
  const gamesListMap = {};
  Object.keys(GAMES.List).forEach(platform => {
    const { today, early } = GAMES.List[platform];
    const todayList = today?.games ?? [];
    const earlyList = early?.games ?? [];
    const timestamp_today = today?.timestamp ?? 0;
    const timestamp_early = early?.timestamp ?? 0;
    const timestamp = Math.max(timestamp_today, timestamp_early);
    gamesListMap[platform] = {
      games: [...todayList, ...earlyList],
      timestamp,
      timestamp_today,
      timestamp_early,
    }
  });
  return gamesListMap;
}

/**
 * 获取比赛盘口
 */
const getGamesEvents = (platform) => {
  if (platform) {
    return Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});
  }
  const gamesEvents = {};
  Object.values(GAMES.Relations).forEach(rel => {
    Object.keys(rel).forEach(platform => {
      const game = rel[platform];
      const { eventId, events, special } = game;
      if (!gamesEvents[platform]) {
        gamesEvents[platform] = {};
      }
      gamesEvents[platform][eventId] = { eventId, events: { ...events, ...special } };
    });
  });
  return gamesEvents;
}

/**
 * 更新关联比赛
 */
const updateGamesRelation = async (relation) => {
  const { id, rel } = relation;
  return Relation.findOne({ id })
  .then(result => {
    if (!result) {
      const gameRelation = new Relation(relation);
      return gameRelation.save();
    }
    return Relation.updateOne({ id }, { $set: { rel } });
  })
  .then(result => {
    GAMES.Relations[id] = rel;
    return result;
  });
}

/**
 * 删除关联比赛
 */
const removeGamesRelation = async (id) => {
  if (!id) {
    return Promise.reject(new Error('ID_INVALID'));
  }
  return Relation.deleteOne({ id })
  .then(result => {
    delete GAMES.Relations[id];
    return result;
  });
}

/**
 * 获取关联比赛
 */
const getGamesRelation = async (listEvents) => {
  const relationIds = Object.keys(GAMES.Relations);
  if (listEvents) {
    return relationIds.map(id => {
      const rel = GAMES.Relations[id];
      return { id, rel };
    });
  }
  return relationIds.map(id => {
    const rel = { ...GAMES.Relations[id] };
    Object.keys(rel).forEach(platform => {
      const game = { ...rel[platform] };
      delete game.events;
      delete game.evtime;
      delete game.special;
      delete game.sptime;
      rel[platform] = game;
    });
    return { id, rel };
  });
}

/**
 * 清理关联比赛
 */
const relationsCleanup = () => {
  const expireTime = Date.now() - 1000*60*5;
  getGamesRelation()
  .then(gamesRelation => {
    gamesRelation.forEach(item => {
      const { id, rel } = item;
      const expire = Object.values(rel).find(event => {
        return event.timestamp <= expireTime;
      });
      if (expire) {
        Logs.out('relation cleanup', id);
        removeGamesRelation(id);
      }
      return true;
    });
  });
}

/**
 * 从数据库中同步关联比赛
 */
const relationSync = () => {
  Relation.find().then(relation => relation.forEach(item => {
    const { id, rel } = item.toObject();
    GAMES.Relations[id] = rel;
  }));
}

/**
 * 更新中单方案
 */
const setSolutions = (solutions) => {
  if (solutions?.length) {
    const solutionsHistory = GAMES.Solutions;
    const updateIds = { add: [], update: [] }
    solutions.forEach(item => {
      const { sid, sol: { win_average } } = item;

      if (!solutionsHistory[sid]) {
        solutionsHistory[sid] = item;
        updateIds.add.push({ sid, win_average });
        return;
      }

      const historyWinAverage = solutionsHistory[sid].sol.win_average;
      if (win_average != historyWinAverage) {
        solutionsHistory[sid] = item;
        updateIds.update.push({ sid, win_average, his_average: historyWinAverage, diff: fixFloat(win_average - historyWinAverage) });
        return;
      }

      const { timestamp } = item;
      solutionsHistory[sid].timestamp = timestamp;

    });
    // if (updateIds.add.length || updateIds.update.length) {
    //   const solutionsList = Object.values(solutionsHistory).sort((a, b) => b.sol.win_average - a.sol.win_average);
    //   Logs.outDev('solutions history update', JSON.stringify(solutionsList, null, 2), JSON.stringify(updateIds, null, 2));
    // }
  }
}

/**
 * 获取中单方案
 */
const getSolutions = async () => {
  const gamesRelations = await getGamesRelation(true);
  const relationsMap = new Map(gamesRelations.map(item => [item.id, item.rel]));
  const solutions = Object.values(GAMES.Solutions).sort((a, b) => b.sol.win_average - a.sol.win_average);
  return solutions.map(item => {
    const { info: { id } } = item;
    const relation = relationsMap.get(id);
    return {
      ...item,
      info: { id, ...relation }
    }
  });
}

/**
 * 清理中单方案
 */
const solutionsCleanup = () => {
  const solutionsHistory = GAMES.Solutions;
  Object.keys(solutionsHistory).forEach(sid => {
    const { timestamp } = solutionsHistory[sid];
    const nowTime = Date.now();
    if (nowTime - timestamp > 1000*60) {
      delete solutionsHistory[sid];
      Logs.out('solution history timeout', sid);
      return;
    }
    const solution = solutionsHistory[sid];
    const eventTime = solution.info.timestamp;
    if (nowTime > eventTime) {
      delete solutionsHistory[sid];
      Logs.out('solution history expired', sid);
    }
  });
}

const getDataFromChild = (type, callback) => {
  const id = ++Request.count;
  Request.callbacks[id] = callback;
  events_child.send({ method: 'get', id, type });
}

events_child.on('message', async (message) => {
  const { callbacks } = Request;
  const { method, id, type, data } = message;
  if (method == 'get' && id) {
    let responseData = null;
    if (type == 'getGamesRelation') {
      responseData = await getGamesRelation(true);
    }
    else if (type == 'getSolutionHistory') {
      responseData = getSolutionHistory();
    }
    events_child.send({ type: 'response', id, data: responseData });
  }
  else if (method == 'post') {
    if (type == 'setSolutions') {
      setSolutions(data);
    }
  }
  else if (method == 'response' && id && callbacks[id]) {
    callbacks[id](data);
    delete callbacks[id];
  }
});

relationSync();
setInterval(() => {
  relationsCleanup();
}, 5000);

setInterval(() => {
  solutionsCleanup();
}, 1000*30);

module.exports = {
  updateGamesList, updateGamesEvents, getGamesList,
  updateGamesRelation, getGamesRelation, removeGamesRelation,
  getGamesEvents, getSolutions,
}