import axios from 'axios';
import path from 'path';
import { fork } from 'child_process';
import { fileURLToPath } from 'url';
import Logs from '../libs/logs.js';
import Cache from '../libs/cache.js';
import Setting from './Setting.js';
import OddsHistory from './OddsHistory.js';
import { eventSolutions } from '../triangle/eventSolutions.js';
import { getPassableEvents, eventsCombination, extractOdds } from '../triangle/trangleCalc.js';
import { calcTotalProfit, calcTotalProfitWithFixedFirst, getFirstInfo } from '../triangle/totalProfitCalc.js';
import { getSetting, updateSetting } from '../triangle/settings.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const GamesCacheFile = path.join(__dirname, '../data/games.cache');

const childOptions = process.env.NODE_ENV == 'development' ? {
  execArgv: ['--inspect=9230'],
  stdio: ['pipe', 'pipe', 'pipe', 'ipc']
} : {};
const eventsChildPath = path.join(__dirname, '../triangle/eventsMatch.js');
const events_child = fork(eventsChildPath, [], childOptions);

const PS_IOR_KEYS = [
  ['0', 'ior_mh', 'ior_mn', 'ior_mc'],
  ['-1', 'ior_rh_15', 'ior_wmh_1', 'ior_rac_05'],
  ['-2', 'ior_rh_25', 'ior_wmh_2', 'ior_rac_15'],
  ['-3', 'ior_rh_35', 'ior_wmh_3', 'ior_rac_25'],
  // ['-4', 'ior_rh_45', 'ior_wmh_4', 'ior_rac_35'],
  // ['-5', 'ior_rh_55', 'ior_wmh_5', 'ior_rac_45'],
  ['+1', 'ior_rah_05', 'ior_wmc_1', 'ior_rc_15'],
  ['+2', 'ior_rah_15', 'ior_wmc_2', 'ior_rc_25'],
  ['+3', 'ior_rah_25', 'ior_wmc_3', 'ior_rc_35'],
  // ['+4', 'ior_rah_35', 'ior_wmc_4', 'ior_rc_45'],
  // ['+5', 'ior_rah_45', 'ior_wmc_5', 'ior_rc_55'],
  ['ou', 'ior_ouc_05', 'ior_ouh_05', 'ior_ouc_15', 'ior_ouh_15', 'ior_ouc_25', 'ior_ouh_25', 'ior_ouc_35', 'ior_ouh_35'],
  ['jqs', 'ior_ot_1', 'ior_ot_2', 'ior_ot_3', 'ior_ot_4', 'ior_ot_5', /*'ior_ot_6', 'ior_ot_7'*/],
];

const IOR_KEYS_TYPE = {
  A: 1, L: 1, R: 1,
  D: 2, K: 2, P: 2,
}

// 测试环境
// const BASE_API_URL = 'https://dev.api.czxd8.com/api/p';
const IS_DEV = process.env.NODE_ENV == 'development';
const BASE_API_URL = IS_DEV ? 'https://cb.long.bid/qbapi' : 'http://172.17.222.37/api';

const GAMES = {
  Leagues: {},
  Baselist: {},
  Relations: {},
  Solutions: {},
  ObOriginalData: {},
  UpdateTimestamp: {},
};

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

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

/**
 * 获取市场类型
 */
const getMarketType = (mk) => {
  if (mk == 0) {
    return 'early';
  }
  else if (mk == 2) {
    return 'rollball';
  }
  return 'today';
}

/**
 * 获取策略类型
 */
const getRuleType = (rule) => {
  const rulePrefix = rule.split(':')[0];
  return IOR_KEYS_TYPE[rulePrefix];
}

/**
 * 关键词匹配比赛
 */
const matchGame = (relation, sk) => {
  const keys = [];
  Object.keys(relation).forEach(platform => {
    if (!relation[platform]) {
      return;
    }
    const { leagueName, teamHomeName, teamAwayName } = relation[platform];
    if (platform == 'ps') {
      keys.push(leagueName);
    }
    keys.push(teamHomeName, teamAwayName);
  });
  return keys.some(key => key?.includes(sk));
}

/**
 * 同步联赛列表
 */
const syncLeaguesList = ({ mk, leagues }) => {
  if (IS_DEV) {
    return Logs.out('syncLeaguesList', { mk, leagues });
  }
  axios.post(`${BASE_API_URL}/p/syncLeague`, { mk, leagues }, { proxy: false })
  .then(res => {
    // Logs.out('syncLeaguesList', res.data);
  })
  .catch(err => {
    Logs.out('syncLeaguesList', err.message);
  });
}

/**
 * 更新联赛列表
 */
const updateLeaguesList = ({ mk, leagues, platform='ps' }) => {

  const { Leagues } = GAMES;

  if (!Leagues[platform]) {
    Leagues[platform] = {};
  }

  const leaguesMap = Leagues[platform];

  const nowTime = Date.now();
  const expireTime = nowTime - 1000 * 60 * 5;
  if (!leaguesMap[mk]) {
    leaguesMap[mk] = {
      timestamp: 0,
      leagues: [],
    };
  }

  if (leaguesMap[mk].timestamp < expireTime ||
    JSON.stringify(leaguesMap[mk].leagues) != JSON.stringify(leagues)) {
    leaguesMap[mk].leagues = leagues;
    leaguesMap[mk].timestamp = nowTime;
    if (platform == 'ps') {
      syncLeaguesList({ mk, leagues });
    }
    return leagues.length;
  }



  return 0;
}

/**
 * 获取筛选过的联赛
 */
const getFilteredLeagues = async (mk) => {
  return axios.get(`${BASE_API_URL}/p/getLeagueTast?mk=${mk ?? ''}`, { proxy: false })
  .then(res => {
    // Logs.out('getFilteredLeagues', res.data);
    if (res.data.code == 0) {
      return res.data.data;
    }
    return Promise.reject(new Error(res.data.message));
  });
}

/**
 * 更新OB原始数据
 */
const updateOriginalData = ({ leagues, matches }) => {
  const { ObOriginalData } = GAMES;
  ObOriginalData.leagues = leagues;
  ObOriginalData.matches = matches;
  ObOriginalData.timestamp = Date.now();
}

/**
 * 获取OB原始数据
 */
const getOriginalData = () => {
  const { ObOriginalData } = GAMES;
  return ObOriginalData;
}

/**
 * 同步比赛列表到服务器
 */
const syncGamesList = ({ platform, mk, games }) => {
  if (IS_DEV) {
    return Logs.out('syncGamesList', { platform, mk, games });
  }
  axios.post(`${BASE_API_URL}/p/syncGames`, { platform, mk, games })
  .then(res => {
    // Logs.out('syncGamesList', { platform, mk, count: games.length }, res.data);
  })
  .catch(err => {
    Logs.out('syncGamesList', { platform, mk }, err.message);
  });
}

/**
 * 同步基准比赛列表
 */
const syncBaseList = ({ marketType, games }) => {
  const baseList = GAMES.Baselist;

  // 直接创建新列表
  if (!baseList[marketType]) {
    baseList[marketType] = games;
    return;
  }

  const newMap = new Map(games.map(item => [item.eventId, item]));

  // 删除不存在的项
  for (let i = baseList[marketType].length - 1; i >= 0; i--) {
    if (!newMap.has(baseList[marketType][i].eventId)) {
      baseList[marketType].splice(i, 1);
    }
  }

  // 添加或更新
  const oldIds = new Set(baseList[marketType].map(item => item.eventId));
  games.forEach(game => {
    if (!oldIds.has(game.eventId)) {
      // 添加新项
      baseList[marketType].push(game);
    }
  });

}

/**
 * 清理基准比赛列表
 */
// const cleanupBaseList = () => {
//   const baseList = GAMES.Baselist;
//   const nowTime = Date.now();
//   const expireTime = nowTime - 1000*60*60*3;
//   Object.keys(baseList).forEach(marketType => {
//     baseList[marketType] = baseList[marketType].filter(item => item.timestamp < expireTime);
//   });
// }

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

    syncGamesList({ platform, mk, games });

    resolve();
  });
});

/**
 * 提交盘口数据
 */
// const submitOdds = ({ platform, mk, games }) => {
//   if (IS_DEV) {
//     return Logs.out('syncOdds', { platform, mk, games });
//   }
//   axios.post(`${BASE_API_URL}/p/syncOdds`, { platform, mk, games}, { proxy: false })
//   .then(res => {
//     // Logs.out('syncOdds', { platform, mk, count: games.length }, res.data);
//   })
//   .catch(err => {
//     Logs.out('syncOdds', { platform, mk }, err.message);
//   });
// }

/**
 * 解析盘口关键词
 */
const parseRatio = (ratioString) => {
  return parseFloat(`${ratioString[0]}.${ratioString.slice(1)}`);
}
const parseIorKey = (iorKey) => {
  const iorKeyMatch = iorKey.match(/^ior_(r|ou|m|wm|ot|os)(a?)(h|c|n)?(_([\d-]+))?$/);
  if (!iorKeyMatch) {
    console.log('no iorKeyMatch', iorKey);
    return null;
  }
  const [, type, accept, side, , ratioString] = iorKeyMatch;
  let ratio = 0;
  if (type === 'ot' || type === 'os') {
    ratio = ratioString;
  }
  else if (ratioString) {
    ratio = parseRatio(ratioString) * (accept ? 1 : -1);
  }
  return { type, side, ratio };
}

/**
 * 格式化盘口数据
 */
const getRouEvents = (events) => {
  const eventsMap = {};
  Object.keys(events).forEach(key => {
    const { type, side, ratio } = parseIorKey(key) ?? {};
    if (!type) {
      return;
    }
    let ratioKey, index;
    if (type === 'r') {
      if (side === 'h') {
        ratioKey = ratio;
        index = 0;
      }
      else if (side === 'c') {
        ratioKey = -ratio;
        index = 1;
      }
    }

    else if (type === 'ou') {
      ratioKey = `ou_${Math.abs(ratio)}`;
      if (side === 'c') {
        index = 0;
      }
      else if (side === 'h') {
        index = 1;
      }
    }

    if (typeof (ratioKey) == 'number') {
      if (ratioKey > 0) {
        ratioKey = `+${ratioKey}`;
      }
      else {
        ratioKey = `${ratioKey}`;
      }
    }

    if (!ratioKey || index === undefined) {
      return;
    }

    if (!eventsMap[ratioKey]) {
      eventsMap[ratioKey] = new Array(2).fill(undefined);
    }

    const v = events[key]?.v ?? 0;
    const r = events[key]?.r;
    const q = events[key]?.q ?? 1;
    eventsMap[ratioKey][index] = { key, v, r, q };
  });

  return eventsMap;
}

/**
 * 限制让胜负/大小球返还率
 */
const rouMaxAdjust = (events) => {
  const { innerReMaxAdjust, innerOuMaxAdjust, innerRouSubsidyValue } = getSetting();
  const rouEvents = getRouEvents(events);
  // Logs.outDev('rouEvents', rouEvents);

  const adjustedEvents = {};
  Object.values(rouEvents).forEach(item => {
    const [a, b] = item;
    if (!a?.v || !b?.v) {
      return;
    }
    const returnRate = (a.v*b.v)/(a.v+b.v)*100;
    let reProportion = 1, ouProportion = 1;
    if (returnRate > innerReMaxAdjust) {
      reProportion = returnRate / innerReMaxAdjust;
    }
    if (returnRate > innerOuMaxAdjust) {
      ouProportion = returnRate / innerOuMaxAdjust;
    }
    item.forEach(ior => {
      const { key, v, r, q } = ior ?? {};
      if (!key || !v) {
        return;
      }
      let proportion = 1;
      if (key.startsWith('ior_r')) {
        proportion = reProportion;
      }
      else if (key.startsWith('ior_ou')) {
        proportion = ouProportion;
      }
      if (proportion <= 1) {
        return;
      }
      adjustedEvents[key] = {
        v: fixFloat(v/proportion, 3),
        r: r,
        s: v,
        q: q,
        rt: fixFloat(returnRate, 3),
      };
    });
  });

  if (events['ior_mh']?.v > adjustedEvents['ior_rh_05']?.v) {
    events['ior_mh'].s = events['ior_mh'].v;
    events['ior_mh'].v = adjustedEvents['ior_rh_05'].v;
    events['ior_mh'].rt = adjustedEvents['ior_rh_05'].rt;
  }

  if (events['ior_mc']?.v > adjustedEvents['ior_rc_05']?.v) {
    events['ior_mc'].s = events['ior_mc'].v;
    events['ior_mc'].v = adjustedEvents['ior_rc_05'].v;
    events['ior_mc'].rt = adjustedEvents['ior_rc_05'].rt;
  }

  if (innerRouSubsidyValue) {
    if (events['ior_rac_05'] === undefined && events['ior_mh']) {
      events['ior_mh'].s = events['ior_mh']?.v ?? 0;
      events['ior_mh'].v = fixFloat(events['ior_mh'].s + innerRouSubsidyValue, 3);
    }
    if (events['ior_rah_05'] === undefined && events['ior_mc']) {
      events['ior_mc'].s = events['ior_mc']?.v ?? 0;
      events['ior_mc'].v = fixFloat(events['ior_mc'].s + innerRouSubsidyValue, 3);
    }
  }

  return Object.assign(events, adjustedEvents);
}

/**
 * 同步基准盘口
 */
const syncBaseEvents = ({ mk, games, outrights }) => {

  const {
    expireTimeEvents, expireTimeSpecial,
    innerWmSubsidyRatio, innerOtSubsidyRatio,
    innerOuMinValue,
  } = getSetting();
  const nowTime = Date.now();

  const marketType = getMarketType(mk);
  const baseList = GAMES.Baselist;
  if (!baseList[marketType]) {
    return 0;
  }

  const baseMap = new Map(baseList[marketType].map(item => [item.eventId, item]));

  games?.forEach(game => {
    const { eventId, originId, stage, retime, score, wm, evtime, events } = game;
    const baseGame = baseMap.get(eventId);
    if (baseGame) {
      OddsHistory.recordGameOdds({ game: { ...baseGame, originId }, events, platform: 'pc' })
      .catch(err => {
        Logs.out('record odds history failed, eventId %s, %s', eventId, err.message);
      });
      const adjustedEvents = rouMaxAdjust(events);
      Object.keys(adjustedEvents).forEach(ior => {
        const regWm = /^ior_(wm|mn)/;
        const regOu = /^ior_ou/;
        const regOt = /^ior_ot/;
        // 平局/让平调水（%）
        if (regWm.test(ior) && innerWmSubsidyRatio) {
          const sourceOdds = adjustedEvents[ior].v;
          adjustedEvents[ior].v = fixFloat(sourceOdds * (1 + innerWmSubsidyRatio / 100), 3);
          adjustedEvents[ior].s = sourceOdds;
        }
        // 进球数调水（%）
        else if (regOt.test(ior) && innerOtSubsidyRatio) {
          const sourceOdds = adjustedEvents[ior].v;
          adjustedEvents[ior].v = fixFloat(sourceOdds * (1 + innerOtSubsidyRatio / 100), 3);
          adjustedEvents[ior].s = sourceOdds;
        }
        // 大小球最低赔率
        if (regOu.test(ior) && innerOuMinValue) {
          const sourceOdds = adjustedEvents[ior].s ?? adjustedEvents[ior].v ?? 1;
          adjustedEvents[ior].q = sourceOdds < innerOuMinValue ? 0 : 1;
        }
      });
      if (typeof originId != 'undefined') {
        baseGame.originId = originId;
      }
      baseGame.stage = stage;
      baseGame.retime = retime;
      baseGame.score = score;
      baseGame.wm = wm;
      baseGame.evtime = evtime;
      baseGame.events = adjustedEvents;
    }
  });

  if (games?.length) {
    const gamesList = baseList[marketType]?.map(game => {
      const { evtime, events, sptime, special, ...gameInfo } = game;
      const expireTimeEv = nowTime - expireTimeEvents;
      const expireTimeSP = nowTime - expireTimeSpecial;
      let odds = {};
      if (evtime > expireTimeEv) {
        odds = { ...odds, ...events };
      }
      if (sptime > expireTimeSP) {
        odds = { ...odds, ...special };
      }
      const matches = PS_IOR_KEYS.map(([label, ...keys]) => {
        let match = keys.map(key => {
          return {
            key,
            value: odds[key]?.v ?? 0,
            origin: odds[key]?.r,
            source: odds[key]?.s,
            qualified: odds[key]?.q ?? 1,
            returnRate: odds[key]?.rt,
          };
        });
        if (label == 'jqs' || label == 'ou') {
          match = match.filter(item => item.value !== 0);
        }
        return {
          label,
          match
        };
      }).filter(item => {
        if (item.label == 'jqs' || item.label == 'ou') {
          return item.match.length;
        }
        else {
          // return item.match.every(entry => {
          //   return entry.value !== 0;
          // });
          const validEntrys = item.match.filter(entry => entry.value !== 0);
          return validEntrys.length >= 2;
        }
      });

      game.matches = matches; // matches 也记录下来

      let uptime = evtime ?? 0;
      return { ...gameInfo, matches, uptime };
    });

    // if (gamesList.filter(item => item.uptime > 0).length) {
    //   // if (mk == 2) {
    //   //   Logs.out('syncBaseEvents', JSON.stringify({ mk, gamesList }, null, 2));
    //   // }
    //   submitOdds({ platform: 'ps', mk, games: gamesList });
    // }

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

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

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

    gamesList?.forEach(game => {
      const { eventId, originId, matches, uptime, stage, retime, score, wm } = game;
      const relatedGame = relatedMap.get(eventId);
      if (relatedGame) {
        const events = {};
        matches.forEach(({ label, match }) => {
          match.forEach(({ key, value, origin, source, qualified, returnRate }) => {
            events[key] = {
              v: value,
              r: origin,
              s: source,
              q: qualified,
              rt: returnRate,
            };
          });
        });
        if (typeof originId != 'undefined') {
          relatedGame.originId = originId;
        }
        relatedGame.evtime = uptime;
        relatedGame.events = events;
        relatedGame.stage = stage;
        relatedGame.retime = retime;
        relatedGame.score = score;
        relatedGame.wm = wm;
        update ++;
      }
    });
    return update;
  }
}

const updateBaseEvents = ({ games, timestamp, tp='ps_9_9_1' }) => {
  return new Promise((resolve, reject) => {
    if (!games) {
      return reject(new Error('GAMES_INVALID'));
    }
    if (!timestamp) {
      return reject(new Error('TIMESTAMP_INVALID'));
    }

    let update = 0;
    const { UpdateTimestamp } = GAMES;
    if (!UpdateTimestamp[tp] || UpdateTimestamp[tp] < timestamp) {
      UpdateTimestamp[tp] = timestamp;
    }
    else {
      return resolve({ update });
    }

    Object.keys(games).forEach(mk => {
      update += syncBaseEvents({ mk, games: games[mk] ?? [] });
    });
    resolve({ update });
  });
}

/**
 * 比较外盘与内盘的赔率
 */
const compareOdds = (events, baseEvents, platform) => {
  const setting = getSetting();
  const maxDiff = setting[`${platform}MaxDiff`] ?? 0;
  Object.keys(events).forEach(ior => {
    const value = events[ior]?.v ?? 0;
    const baseValue = baseEvents[ior]?.s ?? baseEvents[ior]?.v ?? 1;
    if (value - baseValue >= maxDiff) {
      events[ior].q = 1;
    }
    else {
      events[ior].q = 0;
    }
  });
}

const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) => {
  return new Promise((resolve, reject) => {
    if (!platform || (!games && !outrights)) {
      return reject(new Error('PLATFORM_GAMES_INVALID'));
    }

    const { UpdateTimestamp } = GAMES;
    if ((!UpdateTimestamp[tp] || UpdateTimestamp[tp] < timestamp) && (games?.length || outrights?.length)) {
      // Logs.out('updateGamesEvents', { tp, timestamp });
      UpdateTimestamp[tp] = timestamp;
    }
    else {
      return resolve({ update: 0 });
    }

    if (platform == 'ps') {
      const update = syncBaseEvents({ mk, games, outrights });
      return resolve({ update });
    }

    const relatedGames = Object.values(GAMES.Relations).map(item => {
      // item.rel?.[platform] ?? {}
      const { rel } = item ?? {};
      if (!rel) {
        return {};
      }
      const game = rel[platform] ?? {};
      game.baseId = rel['ps']?.eventId;
      return game;
    });

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

    const updateCount = {
      update: 0
    };

    const marketType = getMarketType(mk);
    const baseList = GAMES.Baselist[marketType] ?? [];
    const baseMap = new Map(baseList.map(item => [item.eventId, item]));
    const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));

    games?.forEach(game => {
      const { eventId, originId, evtime, events, stage, retime, score, wm } = game;
      const relatedGame = relatedMap.get(eventId);
      if (!relatedGame) {
        return;
      }
      const baseEvents = baseMap.get(relatedGame.baseId)?.events ?? {};
      compareOdds(events, baseEvents, platform);
      if (relatedGame) {
        if (typeof originId != 'undefined') {
          relatedGame.originId = originId;
        }
        relatedGame.evtime = evtime;
        relatedGame.events = events;
        relatedGame.stage = stage;
        relatedGame.retime = retime;
        relatedGame.score = score;
        relatedGame.wm = wm;

        updateCount.update ++;
      }
    });

    outrights?.forEach(outright => {
      const { parentId, sptime, special } = outright;
      const relatedGame = relatedMap.get(parentId);
      if (!relatedGame) {
        return;
      }
      const baseEvents = baseMap.get(relatedGame.baseId)?.events ?? {};
      compareOdds(special, baseEvents, platform);
      if (relatedGame) {
        const baseGame = baseMap.get(relatedGame.baseId);
        if (platform == 'ob' && baseGame) {
          OddsHistory.recordGameOdds({ game: baseGame, events: special, platform })
          .catch(err => {
            Logs.out('record odds history failed, platform %s, eventId %s, %s', platform, relatedGame.baseId, err.message);
          });
        }
        relatedGame.sptime = sptime;
        relatedGame.special = special;

        updateCount.update ++;
      }
    });

    resolve(updateCount);

  });
}

/**
 * 获取比赛盘口
 */
const getGamesEvents = ({ platform, relIds = [] } = {}) => {
  if (!relIds.length) {
    return null;
  }
  const idSet = new Set(relIds);
  const relations = { ...GAMES.Relations };
  Object.keys(relations).forEach(id => {
    if (idSet.size && !idSet.has(+id)) {
      delete relations[id];
    }
  });

  if (platform) {
    return Object.values(relations).map(rel => rel[platform] ?? {});
  }

  const gamesEvents = {};
  Object.values(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] = { ...events, ...special };
    });
  });
  return gamesEvents;
}


/**
 * 获取远程关联比赛列表
 */
const fetchGamesRelation = async (mk='') => {
  return axios.get(`${BASE_API_URL}/p/getGameTast?mk=${mk}`, {
    proxy: false
  }).then(({ data: resData }) => {
    if (resData.code == 0) {
      const nowTime = Date.now();
      const halfTimeActiveTime = getSetting('halfTimeActiveTime');
      const activeHalfTime = nowTime + halfTimeActiveTime * 60 * 60 * 1000;
      const inactiveHalfTime = nowTime + 60 * 1000;
      const gamesRelation = resData.data?.filter?.((item) => {
        const timestamp = new Date(item.timestamp).getTime();
        const updated = new Date(item.updated_at).getTime();
        if (nowTime > timestamp) {
          item.mk = 2;
        }
        item.timestamp = timestamp;
        item.updated = updated;
        const expireTime = timestamp + 1000*60*60*3;
        return expireTime > nowTime;
      }).map(item => {
        const {
          id, mk, league_name,
          event_id: ps_event_id,
          league_id: ps_league_id,
          team_home_name: ps_team_home_name,
          team_away_name: ps_team_away_name,
          ob_event_id, ob_league_id,
          ob_team_home_name,
          ob_team_away_name,
          hg_event_id, hg_league_id,
          hg_team_home_name,
          hg_team_away_name,
          // im_event_id, im_league_id,
          // im_team_home_name,
          // im_team_away_name,
          updated,
          timestamp,
        } = item;
        const rel = {
          ps: {
            eventId: +ps_event_id,
            leagueId: +ps_league_id,
            leagueName: league_name,
            teamHomeName: ps_team_home_name,
            teamAwayName: ps_team_away_name,
            timestamp
          },
          ob: ob_event_id ? {
            eventId: +ob_event_id,
            leagueId: +ob_league_id,
            leagueName: league_name,
            teamHomeName: ob_team_home_name,
            teamAwayName: ob_team_away_name,
            timestamp
          } : null,
          hg: hg_event_id ? {
            eventId: +hg_event_id,
            leagueId: +hg_league_id,
            leagueName: league_name,
            teamHomeName: hg_team_home_name,
            teamAwayName: hg_team_away_name,
            timestamp
          } : null,
          // im: im_event_id ? {
          //   eventId: +im_event_id,
          //   leagueId: +im_league_id,
          //   leagueName: league_name,
          //   teamHomeName: im_team_home_name,
          //   teamAwayName: im_team_away_name,
          //   timestamp
          // } : null
        };
        const rels = [];
        rels.push({ id: ps_event_id, mk, rel, timestamp, updated });
        if (timestamp < activeHalfTime && timestamp > inactiveHalfTime) {
          const halfRel = {};
          Object.keys(rel).forEach(platform => {
            const game = rel[platform];
            if (game) {
              const { eventId, ...gameInfo } = game;
              halfRel[platform] = {
                eventId: eventId*-1,
                ...gameInfo
              };
            }
            else {
              halfRel[platform] = null;
            }
          });
          rels.push({ id: ps_event_id*-1, mk, rel: halfRel, timestamp, updated });
        }
        return rels;
      }).flat() ?? [];
      return gamesRelation;
    }
    return Promise.reject(new Error(resData.message));
  });
}

/**
 * 获取PC数据
 */
const getPCEvents = (events) => {
  if (!events) {
    return undefined;
  }
  const pcEvents = {};
  Object.keys(events).forEach(ior => {
    const { v, s, r } = events[ior];
    pcEvents[ior] = {
      v: s ?? v,
      r: r
    }
  });
  return pcEvents;
}
const getPCData = (id) => {
  const baseList = Object.values(GAMES.Baselist).flat();
  const baseMap = new Map(baseList.map(item => [item.eventId, item]));
  const baseGame = baseMap.get(id);
  if (!baseGame) {
    return null;
  }
  // Logs.out('getPCData', baseGame);
  const { matches, events, special, ...gameInfo } = baseGame;
  // Logs.out('getPCData gameInfo', gameInfo);
  return {
    events: getPCEvents(events),
    special: getPCEvents(special),
    ...gameInfo
  }
}

/**
 * 获取关联比赛列表
 * @param {object} options
 * @property {number} mk
 * @property {number[]} ids
 * @property {boolean} listEvents
 * @property {boolean} listPC
 * @returns {object[]}
 */
const getGamesRelation = ({
  mk=-1, ids,
  listEvents=false,
  listPC=false,
  listOdds=false,
  hideBase=false,
  hideHalf=false,
  hideOut=false,
} = {}) => {

  let relations;
  if (ids?.length) {
    relations = ids.map(id => GAMES.Relations[id]);
  }
  else {
    relations = Object.values(GAMES.Relations);
  }

  relations = relations.filter(item => {
    if (!item) {
      return false;
    }
    if (hideHalf && item.id < 0) {
      return false;
    }
    if (mk >= 0 && item.mk != mk) {
      return false;
    }
    return true;
  }).sort((a, b) => a.timestamp - b.timestamp);

  if (listEvents && !listPC && !hideBase && !hideHalf) {
    return relations;
  }

  return relations.map(item => {
    const { rel, ...relationInfo } = item;
    const tempRel = { ...rel };
    const { id } = relationInfo;
    if (listPC) {
      tempRel.pc = getPCData(id);
    }
    if (hideBase) {
      delete tempRel.ps;
    }
    if (hideOut) {
      delete tempRel.ob;
      delete tempRel.hg;
    }
    Object.keys(tempRel).forEach(platform => {
      const { events, evtime, special, sptime, ...gameInfo } = tempRel[platform] ?? {};
      if (!listEvents) {
        tempRel[platform] = gameInfo;
      }
      if (listOdds) {
        const { odds, evtime: oddsEvtime, evExpire, spExpire } = extractOdds({ platform, evtime, events, sptime, special });
        tempRel[platform] = { ...tempRel[platform], odds, evtime: oddsEvtime, evExpire, spExpire };
      }
    });
    return { ...relationInfo, rel: tempRel };
  });
}

/**
 * 定时更新关联比赛列表
 */
const updateGamesRelation = () => {
  fetchGamesRelation()
  .then(gamesRelation => {
    const baseList = {};
    gamesRelation.map(item => {
      const baseGame = item.rel?.['ps'] ?? {};
      return { ...baseGame, mk: item.mk };
    }).forEach(item => {
      const marketType = getMarketType(item.mk);
      if (!baseList[marketType]) {
        baseList[marketType] = [];
      }
      baseList[marketType].push(item);
    });

    Object.keys(baseList).forEach(marketType => {
      syncBaseList({ marketType, games: baseList[marketType] });
    });

    const updateCount = {
      add: 0,
      update: 0,
      delete: 0
    };

    gamesRelation.forEach(item => {
      const { id, mk, updated } = item;
      const oldItem = GAMES.Relations[id];
      if (!oldItem) {
        GAMES.Relations[id] = item;
        updateCount.add ++;
      }
      else if (oldItem.mk != mk) {
        GAMES.Relations[id] = item;
        updateCount.update ++;
      }
      else if ((oldItem.updated ?? -1) < updated) {
        GAMES.Relations[id] = item;
        updateCount.update ++;
      }
    });

    const relations = new Set(gamesRelation.map(item => +item.id));

    Object.keys(GAMES.Relations).forEach(id => {
      if (!relations.has(+id)) {
        delete GAMES.Relations[id];
        updateCount.delete ++;
      }
    });

    if (updateCount.add || updateCount.update || updateCount.delete) {
      Logs.out('updateGamesRelation', updateCount);
    }
    else {
      Logs.outDev('updateGamesRelation', updateCount);
    }

  })
  .catch(err => {
    Logs.out('updateGamesRelation', err.message);
  })
  .finally(() => {
    setTimeout(updateGamesRelation, 60000);
  });
}
updateGamesRelation();

const gamesRelationCleanup = () => {
  const relations = Object.values(GAMES.Relations);
  const expireTime = Date.now() - 1000*60;
  relations.forEach(item => {
    const { rel } = item;
    Object.keys(rel).forEach(platform => {
      const { evtime=0, sptime=0 } = rel[platform] ?? {};
      if (evtime && evtime < expireTime) {
        delete rel[platform].events;
        delete rel[platform].evtime;
      }
      if (sptime && sptime < expireTime) {
        delete rel[platform].special;
        delete rel[platform].sptime;
      }
    });
  });
}

/**
 * 同步比赛结果
 */
const syncGamesResult = async (result) => {
  if (IS_DEV) {
    return Logs.out('updateGamesResult', result);
  }
  // axios.post(`${BASE_API_URL}/p/syncMatchResult`, result, { proxy: false })
  // .then(res => {
  //   // Logs.out('syncMatchResult', res.data);
  // })
  // .catch(err => {
  //   Logs.out('syncMatchResult', err.message);
  // });
}

/**
 * 更新比赛结果
 */
const updateGamesResult = (result) => {
  syncGamesResult(result);
  return Promise.resolve();
}

/**
 * 同步中单方案
 */
// const syncSolutions = (solutions) => {
//   if (IS_DEV) {
//     return Logs.out('syncSolutions', solutions);
//   }
//   axios.post(`${BASE_API_URL}/p/syncDsOpportunity`, solutions, { proxy: false })
//   .then(res => {
//     // Logs.out('syncSolutions', res.data);
//   })
//   .catch(err => {
//     Logs.out('syncSolutions', err.message);
//   });
// }

/**
 * 更新中单方案
 */
const getCprKey = (cpr) => {
  const { k, p, v, q } = cpr;
  return `${k}_${p}_${v}_${q}`;
}
const compareCpr = (cpr1, cpr2) => {
  const key1 = getCprKey(cpr1);
  const key2 = getCprKey(cpr2);
  return key1 === key2;
}

const updateSolutions = (solutions, eventsLogsMap) => {

  if (solutions?.length) {
    const solutionsHistory = GAMES.Solutions;
    const updateIds = { add: [], update: [], retain: [], remove: [] }
    solutions.forEach(item => {
      const { sid, cpr, sol: { win_average, win_profit_rate } } = item;

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

      const historySolution = solutionsHistory[sid];
      if (historySolution.sol.win_average !== win_average ||
        historySolution.sol.win_profit_rate !== win_profit_rate ||
        !compareCpr(historySolution.cpr, cpr)) {
        solutionsHistory[sid] = item;
        updateIds.update.push(sid);
        return;
      }

      const { timestamp } = item;
      historySolution.timestamp = timestamp;
      updateIds.retain.push(sid);
    });

    const solutionsMap = new Map(solutions.map(item => [item.sid, item]));
    Object.keys(solutionsHistory).forEach(sid => {
      if (!solutionsMap.has(sid)) {
        delete solutionsHistory[sid];
        updateIds.remove.push(sid);
      }
    });

    // const solutionUpdate = {};
    // Object.keys(updateIds).forEach(key => {
    //   if (key == 'retain' || key == 'remove') {
    //     solutionUpdate[key] = updateIds[key];
    //   }
    //   else {
    //     solutionUpdate[key] = updateIds[key].map(sid => solutionsHistory[sid]);
    //   }
    // });

    // syncSolutions(solutionUpdate);

    if (updateIds.add.length / solutions.length > 0.2 ||
      updateIds.remove.length / solutions.length > 0.2
    ) {
      const { expireEvents, removeEvents } = eventsLogsMap;
      const expireEvemtsMap = {};

      expireEvents.forEach(item => {
        const { mk, platform, info, evExpire, spExpire, evtime, sptime } = item;
        if (!expireEvemtsMap[mk]) {
          expireEvemtsMap[mk] = {};
        }
        if (!expireEvemtsMap[mk][platform]) {
          expireEvemtsMap[mk][platform] = {};
        }
        if (!expireEvemtsMap[mk][platform].list) {
          expireEvemtsMap[mk][platform].list = [];
        }
        if (!expireEvemtsMap[mk][platform].evtime) {
          expireEvemtsMap[mk][platform].evtime = evtime;
        }
        if (!expireEvemtsMap[mk][platform].sptime) {
          expireEvemtsMap[mk][platform].sptime = sptime;
        }
        expireEvemtsMap[mk][platform].list.push({ info, evExpire, spExpire, evtime, sptime });
      });

      Object.keys(expireEvemtsMap).forEach(mk => {
        Object.keys(expireEvemtsMap[mk]).forEach(platform => {
          Logs.out('invalid events, mk %d, platform %s, expire %d, evtime %d, sptime %d',
            mk, platform,
            expireEvemtsMap[mk][platform].list.length,
            expireEvemtsMap[mk][platform].evtime,
            expireEvemtsMap[mk][platform].sptime,
          )
        });
      });

      Logs.out('solutions add %d, update %d, retain %d, remove %d',
        updateIds.add.length, updateIds.update.length, updateIds.retain.length, updateIds.remove.length);
    }
    else {
      Logs.outDev('solutions update complete', updateIds);
    }

  }
}

/**
 * 获取中单方案
 */
const getSolutions = async ({ win_min, with_events, show_lower=false, mk=-1 }) => {
  // Logs.out('getSolutions', win_min);
  const filterMarketType = +mk;
  const { minShowAmount } = getSetting();
  const solutionsList = Object.values(GAMES.Solutions);
  const gamesRelation = getGamesRelation();
  const relationsMap = new Map(gamesRelation.map(item => [item.id, item]));

  const mkCount = {
    all: 0,
    rollball: 0,
    today: 0,
    early: 0,
  }

  let solutions = solutionsList.filter(item => {
    const { sol: { win_average }, lower } = item;
    if (!show_lower && lower) {
      return false;
    }
    return win_average >= (win_min ?? minShowAmount);
  })
  .map(item => {
    const { info: { id } } = item;
    const { mk, rel } = relationsMap.get(id);
    const marketType = getMarketType(mk);
    mkCount.all ++;
    mkCount[marketType] ++;
    return {
      ...item,
      info: { id, mk, ...rel }
    }
  });

  if (mk >= 0) {
    solutions = solutions.filter(item => {
      const { info: { mk } } = item;
      return mk == filterMarketType;
    });
  }

  solutions = solutions.sort((a, b) => b.sol.win_average - a.sol.win_average);

  const relIds = solutions.map(item => item.info.id);
  let gamesEvents;
  if (with_events) {
    gamesEvents = getGamesEvents({ relIds });
  }
  return { solutions, gamesEvents, mkCount };
}

/**
 * 获取中单方案并按照比赛分组
 */
const getGamePeriod = (id) => {
  if (id > 0) {
    return 0;
  }
  else if (id < 0) {
    return 1;
  }
  return -1;
}
const getGamesSolutions = async ({ win_min, with_events, show_lower=false, mk=-1, gp=-1, tp=0, sk }) => {

  const filterMarketType = +mk;
  const filterGamePeriod = +gp;
  const filterDataType = +tp;
  const { minShowAmount } = getSetting();
  const solutionsList = Object.values(GAMES.Solutions);
  const gamesRelation = getGamesRelation({ listEvents: with_events });
  const relationsMap = new Map(gamesRelation.map(item => [item.id, item]));

  const mkCount = {
    all: 0,
    rollball: 0,
    today: 0,
    early: 0,
  }

  const solutionsMap = {};

  solutionsList.forEach(item => {
    const { info: { id }, ...solution } = item;
    const { rule, sol: { win_average }, lower } = solution;
    const ruleType = getRuleType(rule);
    if (!show_lower && lower) {
      return false;
    }
    if ((filterDataType == 0 || filterDataType == ruleType) &&
      (!!sk || win_average >= (win_min ?? minShowAmount)) &&
      (filterGamePeriod == -1 || filterGamePeriod == getGamePeriod(id))
    ) {
      const gameRelation = relationsMap.get(id);
      if (!solutionsMap[id]) {
        solutionsMap[id] = { ...gameRelation, solutions: [] };
      }
      solutionsMap[id].solutions.push(solution);
    }
  })

  const gamesSolutions = Object.values(solutionsMap)
  .filter(item => {
    const { mk, rel, solutions } = item;
    const marketType = getMarketType(mk);
    if (!sk || matchGame(rel, sk)) {
      mkCount.all ++;
      mkCount[marketType] ++;
      solutions.sort((a, b) => b.sol.win_average - a.sol.win_average);
      return filterMarketType == -1 || filterMarketType == mk;
    }
    return false;
  })
  .sort((a, b) => b.solutions[0].sol.win_average - a.solutions[0].sol.win_average);

  return { gamesSolutions, mkCount };
}

/**
 * 获取单个中单方案
 */
const getSolution = async (sid) => {
  if (!sid) {
    return Promise.reject(new Error('sid is required'));
  }
  const solution = GAMES.Solutions[sid];
  if (!solution) {
    return Promise.reject(new Error('solution not found'));
  }
  return solution;
}

/**
 * 通过比赛 ID 获取中单方案
 */
const getSolutionsByIds = async (ids) => {
  const baseList = Object.values(GAMES.Baselist).flat();
  const baseMap = new Map(baseList.map(item => [item.eventId, item]));
  const result = {};
  ids.forEach(id => {
    const baseGame = baseMap.get(id);
    if (baseGame) {
      result[id] = {};
      result[id].matches = baseGame.matches ?? [];
      result[id].sols = [];
    }
    const haflId = id * -1;
    const halfGame = baseMap.get(haflId);
    if (halfGame) {
      result[haflId] = {};
      result[haflId].matches = halfGame.matches ?? [];
      result[haflId].sols = [];
    }
  });
  Object.values(GAMES.Solutions).forEach(item => {
    const { info: { id }, lower } = item;
    if (result[id] && !lower) {
      result[id].sols.push(item);
    }
  });
  return result;
}

/**
 * 获取单场比赛赔率历史
 */
const getOddsHistory = async (eventId) => {
  return OddsHistory.getGameOddsHistory(eventId);
}

/**
 * 获取赔率历史比赛列表
 */
const getOddsHistoryGames = async (options) => {
  return OddsHistory.getOddsHistoryGames(options);
}

/**
 * 清理中单方案
 */
const solutionsCleanup = () => {
  const solutionsHistory = GAMES.Solutions;
  const updateIds = { remove: [] }
  Object.keys(solutionsHistory).forEach(sid => {
    const { timestamp } = solutionsHistory[sid];
    const nowTime = Date.now();
    if (nowTime - timestamp > 1000*60) {
      delete solutionsHistory[sid];
      updateIds.remove.push(sid);
      return;
    }
    const solution = solutionsHistory[sid];
    const eventTime = solution.info.timestamp;
    if (nowTime > eventTime) {
      delete solutionsHistory[sid];
      updateIds.remove.push(sid);
    }
  });

  // if (updateIds.remove.length) {
  //   syncSolutions(updateIds);
  // }
}

/**
 * 清理更新时间戳
 */
const cleanupUpdateTimestamp = () => {
  const updateTimestamp = GAMES.UpdateTimestamp;
  const nowTime = Date.now();
  const expireTime = nowTime - 1000*60;
  Object.keys(updateTimestamp).forEach(key => {
    if (updateTimestamp[key] < expireTime) {
      delete updateTimestamp[key];
    }
  });
}

/**
 * 定时清理中单方案
 * 定时清理盘口信息
 */
setInterval(() => {
  // cleanupBaseList();
  solutionsCleanup();
  gamesRelationCleanup();
  cleanupUpdateTimestamp();
}, 1000*30);

/**
 * 获取综合利润
 */
const getTotalProfit = async (sol1, sol2, inner_base, inner_rebate) => {
  const { innerDefaultAmount, innerRebateRatio } = getSetting();
  inner_base = inner_base ? +inner_base : innerDefaultAmount;
  inner_rebate = inner_rebate ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
  const profit = calcTotalProfit(sol1, sol2, inner_base, inner_rebate);
  return profit;
}

/**
 * 通过 sid 获取综合利润
 */
const getTotalProfitWithSid = async (sid1, sid2, inner_base, inner_rebate) => {
  const preSolution = GAMES.Solutions[sid1];
  const subSolution = GAMES.Solutions[sid2];
  const sol1 = preSolution?.sol;
  const sol2 = subSolution?.sol;
  if (!sol1) {
    return Promise.reject(new Error('sid1 已失效'));
  }
  if (!sol2) {
    return Promise.reject(new Error('sid2 已失效'));
  }
  const profit = await getTotalProfit(sol1, sol2, inner_base, inner_rebate);
  return { profit, solutions: [preSolution, subSolution] };
}

/**
 * 通过盘口信息获取综合利润
 */
const getTotalProfitWithBetInfo = async (betInfo1, betInfo2, fixed=false, inner_base, inner_rebate) => {
  if (!betInfo1?.cross_type) {
    return Promise.reject(new Error('第一个下注信息无效'));
  }
  if (!betInfo2?.cross_type) {
    return Promise.reject(new Error('第二个下注信息无效'));
  }
  const { innerDefaultAmount, innerRebateRatio } = getSetting();
  inner_base = typeof(inner_base) != 'undefined' ? +inner_base : innerDefaultAmount;
  inner_rebate = typeof(inner_rebate) != 'undefined' ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);

  if (fixed) {
    return calcTotalProfitWithFixedFirst(betInfo1, betInfo2, inner_base, inner_rebate);
  }

  const [sol1, sol2] = [betInfo1, betInfo2].map(betinfo => eventSolutions({...betinfo, inner_base, inner_rebate }));
  return getTotalProfit(sol1, sol2, inner_base, inner_rebate);
}

/**
 * 计算补单综合利润
 */
const getTotalReplacement = async (data) => {
  const { event_id, inner_ior, inner_odds, pre_bet_info } = data;
  if (!event_id) {
    Logs.out('getTotalReplacement event_id 无效', data);
    return Promise.reject(new Error('event_id 无效'));
  }
  if (!inner_ior) {
    Logs.out('getTotalReplacement inner_ior 无效', data);
    return Promise.reject(new Error('inner_ior 无效'));
  }
  if (isNaN(+inner_odds)) {
    Logs.out('getTotalReplacement inner_odds 无效', data);
    return Promise.reject(new Error('inner_odds 无效'));
  }
  if (!pre_bet_info || typeof(pre_bet_info) !== 'object') {
    Logs.out('getTotalReplacement pre_bet_info 无效', data);
    return Promise.reject(new Error('pre_bet_info 无效'));
  }

  const { loss_out, inner_ref_value: inner_base, inner_odds: inner_odds_first } = getFirstInfo(pre_bet_info);

  const { innerRebateRatio } = getSetting();
  const inner_rebate = typeof(data.inner_rebate) != 'undefined' ? +data.inner_rebate : fixFloat(innerRebateRatio / 100, 3);

  const inner_odds_value = fixFloat(inner_odds * (inner_odds_first+1), 3);

  const relation = getGamesRelation({ ids: [event_id], listEvents: true, listPC: true }).map(item => {
    const { rel, ...relationInfo } = item;
    const tempRel = { ...rel };
    const events = {};
    const evtime = Date.now();
    events[inner_ior] = { v: inner_odds_value };
    tempRel.ps = { ...rel.ps, events, evtime, special: null, sptime: 0 }
    return { ...relationInfo, rel: tempRel };
  });

  if (!relation?.length) {
    Logs.out('getTotalReplacement 没有找到对应的比赛 data %o, relation %o', data, relation);
    return Promise.reject(new Error('没有找到对应的比赛'));
  }

  const passableEvents = getPassableEvents(relation, null);
  const solutions = eventsCombination(passableEvents, inner_base, inner_rebate);

  if (!solutions?.length) {
    Logs.out('getTotalReplacement 没有可用的解决方案 data %o, relation %o, passableEvents %o, solutions %o', data, relation, passableEvents, solutions);
    return Promise.reject(new Error('没有可用的解决方案'));
  }

  Logs.out('getTotalReplacement, event_id[%d], inner_ior[%s], solutions[%d]', event_id, inner_ior, solutions.length);

  // 过滤 .filter(solution => !solution.lower)
  return solutions.map(solution => {
    const { sid, info, sol, ...solutionInfo } = solution;
    const { win_profit_rate, win_average_rate, ...solInfo } = sol;
    const { cross_type } = solInfo;
    solInfo.win_side_a = typeof(solInfo.win_side_a) !== 'number' ? undefined : fixFloat(solInfo.win_side_a - loss_out);
    solInfo.win_side_b = typeof(solInfo.win_side_b) !== 'number' ? undefined : fixFloat(solInfo.win_side_b - loss_out);
    solInfo.win_side_c = cross_type == 'la_wa_rv' ? 0 : (typeof(solInfo.win_side_c) !== 'number' ? undefined : fixFloat(solInfo.win_side_c - loss_out));
    solInfo.win_average = fixFloat(solInfo.win_average - loss_out);

    return { ...solutionInfo, sol: solInfo };
  });

}

/**
 * 同步Qboss平台配置
 */
const syncQbossConfig = () => {
  const setting = getSetting();
  if (!setting.syncSettingEnabled) {
    // Logs.outDev('syncQbossConfig disabled');
    return setTimeout(syncQbossConfig, 1000*5);
  }
  axios.get(`${BASE_API_URL}/p/QbossSystemConfig`, { proxy: false })
  .then(res => {
    const { data } = res;
    Logs.outDev('QbossSystemConfig', data);
    if (!data?.data) {
      throw new Error('QbossSystemConfig data is empty');
    }
    const {
      qboss_return_ratio,
      ob_return_ratio, ob_return_type, ob_odds_more_than,
      im_return_ratio, im_return_type, im_odds_more_than,
      hg_return_ratio, hg_return_type, hg_odds_more_than,
      pc_return_ratio, pc_return_type,
      // qboss_jq_add_odds, qboss_jq_add_hours,
      // qboss_gq_add_dy_odds, qboss_gq_add_jq_odds,
      bc_before_hours,
      // qboss_ts_rqdx_max_return_rate_adjust,
      qboss_ts_rq_max_return_rate_adjust,
      qboss_ts_dx_max_return_rate_adjust,
      qboss_ts_sfdx_odds_add, qboss_ts_pjrq_odds_add, qboss_ts_jqs_odds_add,
     } = data.data;
    const qbossSetting = {
      innerRebateRatio: +(qboss_return_ratio ?? '0'),
      obRebateRatio: +(ob_return_ratio ?? '0'),
      obRebateType: +(ob_return_type ?? '0'),
      obMaxDiff: +(ob_odds_more_than ?? '0'),
      imRebateRatio: +(im_return_ratio ?? '0'),
      imRebateType: +(im_return_type ?? '0'),
      imMaxDiff: +(im_odds_more_than ?? '0'),
      hgRebateRatio: +(hg_return_ratio ?? '0'),
      hgRebateType: +(hg_return_type ?? '0'),
      hgMaxDiff: +(hg_odds_more_than ?? '0'),
      pcRebateRatio: +(pc_return_ratio ?? '0'),
      pcRebateType: +(pc_return_type ?? '0'),
      // subsidyTime:  +(qboss_jq_add_hours ?? '0'),
      // subsidyAmount: +(qboss_jq_add_odds ?? '0'),
      // subsidyRbWmAmount: +(qboss_gq_add_dy_odds ?? '0'),
      // subsidyRbOtAmount: +(qboss_gq_add_jq_odds ?? '0'),
      innerRouSubsidyValue: +(qboss_ts_sfdx_odds_add ?? '0'),
      // innerRouMaxAdjust: +(qboss_ts_rqdx_max_return_rate_adjust ?? '100'),
      innerReMaxAdjust: +(qboss_ts_rq_max_return_rate_adjust ?? '100'),
      innerOuMaxAdjust: +(qboss_ts_dx_max_return_rate_adjust ?? '100'),
      innerWmSubsidyRatio: +(qboss_ts_pjrq_odds_add ?? '0'),
      innerOtSubsidyRatio: +(qboss_ts_jqs_odds_add ?? '0'),
      halfTimeActiveTime: +(bc_before_hours ?? '0'),
    };
    Logs.outDev('Sync QbossSystemConfig', qbossSetting);
    const settingFields = {};
    let needUpdate = false;
    Object.keys(qbossSetting).forEach(key => {
      if (qbossSetting[key] !== setting[key]) {
        settingFields[key] = qbossSetting[key];
        needUpdate = true;
      }
    });
    if (needUpdate) {
      Setting.update(settingFields);
      // Logs.outDev('syncQbossConfig', settingFields);
    }
    // else {
    //   Logs.outDev('syncQbossConfig no change');
    // }
  })
  .catch(err => {
    Logs.out('Sync QbossSystemConfig error', err.message);
  })
  .finally(() => {
    setTimeout(syncQbossConfig, 1000*15);
  });
}
syncQbossConfig();

/**
 * 异常通知
 */
const notifyException = (message) => {
  if (IS_DEV) {
    return Logs.out('notifyException', { message });
  }
  const chat_id = -1003032820471;
  Promise.all([
    axios.get(`${BASE_API_URL}/telegram/jump`, { params: { chat_id, message: `@taoger369 ${message}` } }, { proxy: false }),
    axios.post('https://push.long.bid/bark', {
      title: 'QBoss异常通知',
      content: message,
      topic: 'default',
      icon: 'https://bwh.flyzto.com/icon/ball.png',
    }, { proxy: false }),
  ])
  .then(() => {
    Logs.out('notifyException', '通知成功');
  })
  .catch(err => {
    Logs.out('notifyException', err.message);
  });
}

/**
 * 从子进程获取数据
 */
const getDataFromChild = (type, callback) => {
  const id = ++Request.count;
  Request.callbacks[id] = callback;
  events_child.send({ method: 'get', id, type });
}

/**
 * 向子进程发送数据
 */
const postDataToChild = (type, data) => {
  events_child.send({ method: 'post', type, data });
}

/**
 * 处理子进程消息
 */
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 = getGamesRelation({ listEvents: true });
    }
    else if (type == 'getSetting') {
      responseData = getSetting();
    }
    // else if (type == 'getSolutionHistory') {
    //   responseData = getSolutionHistory();
    // }
    events_child.send({ type: 'response', id, data: responseData });
  }
  else if (method == 'post') {
    if (type == 'updateSolutions') {
      updateSolutions(data.solutions, data.eventsLogsMap);
    }
  }
  else if (type == 'response' && id && callbacks[id]) {
    callbacks[id](data);
    delete callbacks[id];
  }
});

events_child.stderr?.on('data', data => {
  Logs.out('events_child stderr', data.toString());
});

const settingUpdate = (fields) => {
  updateSetting(fields);
  postDataToChild('updateSetting', fields);
}

const syncSetting = async () => {
  const setting = await Setting.get();
  settingUpdate(setting.toObject());
}

syncSetting();

Setting.onUpdate(settingUpdate);

/**
 * 保存GAMES数据到缓存文件
 */
const saveGamesToCache = () => {
  Cache.setData(GamesCacheFile, GAMES, err => {
    if (err) {
      Logs.out('Failed to save games cache:', err.message);
    }
    else {
      Logs.out('Games cache saved successfully');
    }
  });
}

/**
 * 从缓存文件加载GAMES数据
 */
const loadGamesFromCache = () => {
  const gamesCacheData = Cache.getData(GamesCacheFile, true) ?? {};
  Object.keys(GAMES).forEach(key => {
    if (key in gamesCacheData) {
      GAMES[key] = gamesCacheData[key];
    }
  });
  Logs.out('Games cache loaded successfully');
}

// 在模块加载时尝试从缓存恢复数据
loadGamesFromCache();
OddsHistory.startCleanup(Logs);

// 监听进程退出事件，保存GAMES数据
process.on('exit', saveGamesToCache);
process.on('SIGINT', () => {
  process.exit(0);
});
process.on('SIGTERM', () => {
  process.exit(0);
});
process.on('SIGUSR2', () => {
  process.exit(0);
});


const GamesPs = {
  updateLeaguesList, getFilteredLeagues,
  updateGamesList, updateGamesEvents, updateBaseEvents,
  getGamesRelation,
  updateGamesResult,
  updateOriginalData, getOriginalData,
  getSolutions, getGamesSolutions, getSolution, getSolutionsByIds,
  getOddsHistory, getOddsHistoryGames,
  getTotalProfitWithSid, getTotalProfitWithBetInfo, getTotalReplacement,
  notifyException,
};

export {
  updateLeaguesList, getFilteredLeagues,
  updateGamesList, updateGamesEvents, updateBaseEvents,
  getGamesRelation,
  updateGamesResult,
  updateOriginalData, getOriginalData,
  getSolutions, getGamesSolutions, getSolution, getSolutionsByIds,
  getOddsHistory, getOddsHistoryGames,
  getTotalProfitWithSid, getTotalProfitWithBetInfo, getTotalReplacement,
  notifyException,
};

export default GamesPs;
