import crypto from 'crypto';
import IOR_KEYS_MAP from './iorKeys.js';
import { getSetting } from './settings.js';
import { eventSolutions } from './eventSolutions.js';

const cartesianOdds = (selection) => {
  const [a, b, c] = selection;
  return a.flatMap(itemA => b.flatMap(itemB => c.map(itemC => [itemA, itemB, itemC])));
}

const getOptimalSelections = (odds, rules) => {
  const results = [];

  rules.forEach((rule, ruleIndex) => {
    let validOptions = [];

    for (let i = 0; i < 3; i++) {
      const innerIndex = i;
      const selection = [];
      let isValid = true;
      for (let j = 0; j < 3; j++) {
        const key = rule[j];
        let item = odds[key];
        if (key == '-') {
          item = { no: { v: 1 } };
        }
        if (!item) {
          isValid = false;
          break;
        }
        if (j === innerIndex) {
          if (!('ps' in item)) {
            isValid = false;
            break;
          }
          selection.push([{
            k: key,
            p: 'ps',
            o: item,
            ...item['ps'],
          }]);
        }
        else {
          const { ps, ...itemRest } = item;
          const candidates = Object.keys(itemRest);
          if (candidates.length === 0) {
            isValid = false;
            break;
          }
          selection.push(candidates.map(k => ({
            k: key,
            p: k,
            o: item,
            ...item[k],
          })));
        }
      }

      if (isValid) {
        const cartesian = cartesianOdds(selection);
        cartesian.forEach(item => {
          validOptions.push(item);
        });
      }
    }

    validOptions.forEach(iors => {
      results.push({ rule, iors, ruleIndex });
    });
  });

  return results;
}

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

/**
 * 盘口排序
 */
const priority = { ps: 1, ob: 2, hg: 3, im: 4, pc: 5 };
const sortCpr = (cpr) => {
  const temp = [...cpr];
  temp.sort((a, b) => priority[a.p] - priority[b.p]);
  return temp;
}

/**
 * 获取平台类型
 */
const getPlatformKey = (cpr) => {
  const platforms = sortCpr(cpr).map(item => item.p);
  return [...new Set(platforms)].join('_');
}

/**
 * 添加返佣
 */
const attachRebate = (ior, k, p) => {
  if (!ior) {
    return;
  }
  if (!k || !p) {
    return { ...ior };
  }
  const { obRebateRatio, obRebateType, hgRebateRatio, hgRebateLower, hgRebateType, pcRebateRatio, pcRebateType } = getSetting();
  let rebate = 0, rebateType = 0;
  if (p == 'ps') {
    rebate = 0;
    rebateType = -1;
  }
  else if (p == 'pc') {
    rebate = pcRebateRatio;
    rebateType = pcRebateType;
  }
  else if (p == 'ob') {
    rebate = obRebateRatio;
    rebateType = obRebateType;
  }
  else if (p == 'hg') {
    rebate = hgRebateRatio;
    rebateType = hgRebateType;
    if (k.startsWith('ior_m')) {
      rebate = hgRebateLower;
    }
  }
  return { ...ior, b: rebate, t: rebateType };
}

/**
 * 提取盘口数据
 */
const extractOdds = ({ platform, evtime=0, events, sptime=0, special }) => {
  const { expireTimeEvents, expireTimeSpecial } = getSetting();
  const nowTime = Date.now();
  const expireTimeEv = nowTime - expireTimeEvents;
  const expireTimeSP = nowTime - expireTimeSpecial;
  const extractData = {
    odds: null,
    evExpire: false,
    spExpire: false,
    evtime: 0,
    removeCount: 0,
  }
  let odds = {};
  if (evtime > expireTimeEv) {
    odds = { ...odds, ...events };
  }
  else if (events) {
    extractData.evExpire = true;
  }
  if (sptime > expireTimeSP) {
    odds = { ...odds, ...special };
  }
  else if (special) {
    extractData.spExpire = true;
  }
  Object.keys(odds).forEach(ior => {
    if (!odds[ior]?.v || odds[ior].v <= 0) {
      delete odds[ior];
      extractData.removeCount++;
    }
    else {
      odds[ior] = attachRebate(odds[ior], ior, platform);
    }
  });
  extractData.odds = odds;
  extractData.evtime = Math.max(evtime, sptime);
  return extractData;
}

/**
 * 筛选有效盘口
 */
const getPassableEvents = (relations, eventsLogsMap) => {
  return relations.map(({ id, rel, mk }) => {
    const eventsMap = {};
    const oddsMap = {};

    Object.keys(rel).forEach(platform => {
      const { leagueName, teamHomeName, teamAwayName, timestamp, stage, score, retime, evtime, events, sptime, special } = rel[platform] ?? {};
      if (!events && !special) {
        return;
      }
      if (platform == 'ps') {
        eventsMap.info = { leagueName, teamHomeName, teamAwayName, id, timestamp, stage, score, retime };
      }
      const { odds, evExpire, spExpire, removeCount } = extractOdds({ platform, evtime, events, sptime, special });

      /** 日志 盘口过期 */
      if (eventsLogsMap && (evExpire || spExpire)) {
        eventsLogsMap.expireEvents?.push({
          mk, platform,
          info: rel[platform],
          evExpire, spExpire,
          evtime, sptime,
        });
      }
      /** 日志 盘口移除 */
      if (eventsLogsMap && removeCount) {
        eventsLogsMap.removeEvents?.push({
          mk, platform,
          info: rel[platform],
          removeCount,
        });
      }
      /** 日志 End */

      Object.keys(odds).forEach(ior => {
        if (!oddsMap[ior]) {
          oddsMap[ior] = {};
        }
        oddsMap[ior][platform] = odds[ior];
      });
    });
    eventsMap.odds = oddsMap;
    return eventsMap;
  })
  .filter(item => item?.info);
}

/**
 * 盘口组合计算
 */
const eventsCombination = (passableEvents, innerBase, innerRebate) => {
  const { innerDefaultAmount, innerRebateRatio } = getSetting();
  const solutions = [];
  passableEvents.forEach(events => {
    const { odds, info } = events;
    Object.keys(IOR_KEYS_MAP).forEach(iorGroup => {
      const rules = IOR_KEYS_MAP[iorGroup];
      const optimalSelections = getOptimalSelections(odds, rules);
      optimalSelections.forEach(selection => {
        const { rule, iors, ruleIndex } = selection;
        const [, , , crossType] = rule;
        // const oddsSideA = iors[0];
        // const oddsSideB = iors[1];
        // const oddsSideC = iors[2];
        const innerIndex = iors.findIndex(item => item.p == 'ps');
        if (iors.some(item => !item?.v || item.v <= 0)) {
          return;
        }
        const cpr = [...iors];
        const betInfo = {
          cross_type: crossType,
          inner_index: innerIndex,
          inner_base: innerBase ?? innerDefaultAmount,
          inner_rebate: innerRebate ?? fixFloat(innerRebateRatio / 100, 3),
          odds_side_a: fixFloat(iors[0].v - 1),
          odds_side_b: fixFloat(iors[1].v - 1),
          odds_side_c: fixFloat(iors[2].v - 1),
          rebate_side_a: fixFloat(((iors[0].b ?? 0) / 100), 4),
          rebate_type_side_a: iors[0].t ?? 0,
          rebate_side_b: fixFloat(((iors[1].b ?? 0) / 100), 4),
          rebate_type_side_b: iors[1].t ?? 0,
          rebate_side_c: fixFloat(((iors[2].b ?? 0) / 100), 4),
          rebate_type_side_c: iors[2].t ?? 0,
        };
        const sol = eventSolutions(betInfo, true);
        if (cpr[2].k == '-') {
          cpr.pop();
        }
        if (!isNaN(sol?.win_average)) {
          const id = info.id;
          const keys = cpr.map(item => `${item.k}-${item.p}`).join('##');
          const sid = crypto.createHash('sha1').update(`${id}-${keys}`).digest('hex');
          const crpGroup = `${id}_${cpr.find(item => item.p == 'ps').k}`;
          const hasLower = cpr.some(item => item.q === 0);
          const platformKey = getPlatformKey(cpr);
          const timestamp = Date.now();
          solutions.push({sid, sol, cpr, cross: platformKey, info, group: crpGroup, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
        }
      });
    });
  });
  return solutions.sort((a, b) => {
    return b.sol.win_average - a.sol.win_average;
  });
}

export { eventsCombination, getPassableEvents, extractOdds };
export default { eventsCombination, getPassableEvents, extractOdds };
