import 'dotenv/config';

import { pinnacleRequest, getPsteryRelations, updateBaseEvents, notifyException } from "./libs/pinnacleClient.js";
import { Logs } from "./libs/logs.js";
import { getData, setData } from "./libs/cache.js";


const gamesMapCacheFile = 'data/gamesCache.json';
const globalDataCacheFile = 'data/globalDataCache.json';
const TP = 'ps_9_9_1';
const IS_DEV = process.env.NODE_ENV == 'development';

const GLOBAL_DATA = {
  filtedLeagues: [],
  filtedGames: [],
  gamesMap: {},
  straightFixturesVersion: 0,
  straightFixturesCount: 0,
  specialFixturesVersion: 0,
  specialFixturesCount: 0,
  straightOddsVersion: 0,
  // straightOddsCount: 0,
  specialsOddsVersion: 0,
  // specialsOddsCount: 0,
  requestErrorCount: 0,
  loopActive: false,
  loopResultTime: 0,
};

const resetVersionsCount = () => {
  GLOBAL_DATA.straightFixturesVersion = 0;
  GLOBAL_DATA.specialFixturesVersion = 0;
  GLOBAL_DATA.straightOddsVersion = 0;
  GLOBAL_DATA.specialsOddsVersion = 0;

  GLOBAL_DATA.straightFixturesCount = 0;
  GLOBAL_DATA.specialFixturesCount = 0;
}

const incrementVersionsCount = () => {
  GLOBAL_DATA.straightFixturesCount = 0;
  GLOBAL_DATA.specialFixturesCount = 0;
}


/**
 * 获取指定时区当前日期或时间
 * @param {number} offsetHours - 时区相对 UTC 的偏移（例如：-4 表示 GMT-4）
 * @param {boolean} [withTime=false] - 是否返回完整时间（默认只返回日期）
 * @returns {string} 格式化的日期或时间字符串
 */
const getDateInTimezone = (offsetHours, withTime = false) => {
  const nowUTC = new Date();
  const targetTime = new Date(nowUTC.getTime() + offsetHours * 60 * 60 * 1000);

  const year = targetTime.getUTCFullYear();
  const month = String(targetTime.getUTCMonth() + 1).padStart(2, '0');
  const day = String(targetTime.getUTCDate()).padStart(2, '0');

  if (!withTime) {
    return `${year}-${month}-${day}`;
  }

  const hours = String(targetTime.getUTCHours()).padStart(2, '0');
  const minutes = String(targetTime.getUTCMinutes()).padStart(2, '0');
  const seconds = String(targetTime.getUTCSeconds()).padStart(2, '0');

  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}


const pinnacleGet = async (endpoint, params) => {
  return pinnacleRequest({
    endpoint,
    params,
    username: process.env.PINNACLE_USERNAME,
    password: process.env.PINNACLE_PASSWORD,
    proxy: process.env.NODE_HTTP_PROXY,
  })
  .catch(err => {
    const source = { endpoint, params };
    if (err?.response?.data) {
      const data = err.response.data;
      Object.assign(source, { data });
    }
    err.source = source;
    return Promise.reject(err);
  });
};


// const pinnaclePost = async(endpoint, data) => {
//   return pinnacleRequest({
//     endpoint,
//     data,
//     method: 'POST',
//     username: process.env.PINNACLE_USERNAME,
//     password: process.env.PINNACLE_PASSWORD,
//     proxy: process.env.NODE_HTTP_PROXY,
//   });
// };


const isArrayEqualUnordered = (arr1, arr2) => {
  if (arr1.length !== arr2.length) return false;
  const s1 = [...arr1].sort();
  const s2 = [...arr2].sort();
  return s1.every((v, i) => v === s2[i]);
}


const getFiltedGames = () => {
  return new Promise(resolve => {
    const updateFiltedGames = () => {
      getPsteryRelations()
      .then(res => {
        if (res.statusCode !== 200) {
          throw new Error(res.message);
        }
        // Logs.outDev('update filted games', res.data);
        const games = res.data.map(game => {
          const { eventId, leagueId } = game?.rel?.ps ?? {};
          return {
            eventId,
            leagueId,
          };
        });

        const newFiltedLeagues = [...new Set(games.map(game => game.leagueId).filter(leagueId => leagueId))];

        if (!isArrayEqualUnordered(newFiltedLeagues, GLOBAL_DATA.filtedLeagues)) {
          Logs.outDev('filted leagues changed', newFiltedLeagues);
          resetVersionsCount();
          GLOBAL_DATA.filtedLeagues = newFiltedLeagues;
        }

        GLOBAL_DATA.filtedGames = games.map(game => game.eventId).filter(eventId => eventId);

        resolve();
        setTimeout(updateFiltedGames, 1000 * 60);
      })
      .catch(err => {
        Logs.err('failed to update filted games:', err.message);
        setTimeout(updateFiltedGames, 1000 * 5);
      });
    }
    updateFiltedGames();
  });
}


const getStraightFixtures = async () => {
  if (!GLOBAL_DATA.filtedLeagues.length) {
    return Promise.reject(new Error('no filted leagues'));
  }
  const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  let since = GLOBAL_DATA.straightFixturesVersion;
  if (GLOBAL_DATA.straightFixturesCount >= 12) {
    since = 0;
    GLOBAL_DATA.straightFixturesCount = 0;
  }
  if (since == 0) {
    Logs.outDev('full update straight fixtures');
  }
  return pinnacleGet('/v3/fixtures', { sportId: 29, leagueIds, since })
  .then(data => {
    const { league, last } = data;
    if (!last) {
      return {};
    }
    GLOBAL_DATA.straightFixturesVersion = last;
    const games = league?.map(league => {
      const { id: leagueId, events } = league;
      return events?.map(event => {
        const { starts } = event;
        const timestamp = new Date(starts).getTime();
        return { leagueId, ...event, timestamp };
      });
    })
    .flat() ?? [];
    const update = since == 0 ? 'full' : 'increment';
    return { games, update };
  });
}


const updateStraightFixtures = async () => {
  return getStraightFixtures()
  .then(data => {
    const { games, update } = data;
    const { gamesMap } = GLOBAL_DATA;
    if (games?.length) {
      games.forEach(game => {
        const { id } = game;
        if (!gamesMap[id]) {
          gamesMap[id] = game;
        }
        else {
          Object.assign(gamesMap[id], game);
        }
      });
    }
    if (update && update == 'full') {
      const gamesSet = new Set(games.map(game => game.id));
      Object.keys(gamesMap).forEach(key => {
        if (!gamesSet.has(+key)) {
          delete gamesMap[key];
        }
      });
    }
  });
}


const getStraightOdds = async () => {
  if (!GLOBAL_DATA.filtedLeagues.length) {
    return Promise.reject(new Error('no filted leagues'));
  }
  const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  const since = GLOBAL_DATA.straightOddsVersion;
  // if (GLOBAL_DATA.straightOddsCount >= 27) {
  //   since = 0;
  //   GLOBAL_DATA.straightOddsCount = 3;
  // }
  if (since == 0) {
    Logs.outDev('full update straight odds');
  }
  return pinnacleGet('/v3/odds', { sportId: 29, oddsFormat: 'Decimal', leagueIds, since })
  .then(data => {
    const { leagues, last } = data;
    if (!last) {
      return [];
    }
    GLOBAL_DATA.straightOddsVersion = last;
    const games = leagues?.flatMap(league => league.events);
    return games?.map(item => {
      const { periods, ...rest } = item;
      const period = periods?.find(period => period.number == 0);
      if (!period) {
        return rest;
      }
      return { ...rest, period };
    }) ?? [];
  });
}


const updateStraightOdds = async () => {
  return getStraightOdds()
  .then(games => {
    if (games.length) {
      const { gamesMap } = GLOBAL_DATA;
      games.forEach(game => {
        const { id, ...rest } = game;
        const localGame = gamesMap[id];
        if (localGame) {
          Object.assign(localGame, rest);
        }
      });
    }
  });
}


const getSpecialFixtures = async () => {
  if (!GLOBAL_DATA.filtedLeagues.length) {
    return Promise.reject(new Error('no filted leagues'));
  }
  const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  let since = GLOBAL_DATA.specialFixturesVersion;
  if (GLOBAL_DATA.specialFixturesCount >= 18) {
    since = 0;
    GLOBAL_DATA.specialFixturesCount = 6;
  }
  if (since == 0) {
    Logs.outDev('full update special fixtures');
  }
  return pinnacleGet('/v2/fixtures/special', { sportId: 29, leagueIds, since })
  .then(data => {
    const { leagues, last } = data;
    if (!last) {
      return [];
    }
    GLOBAL_DATA.specialFixturesVersion = last;
    const specials = leagues?.map(league => {
      const { specials } = league;
      return specials?.filter(special => special.event)
      .map(special => {
        const { event: { id: eventId }, ...rest } = special ?? { event: {} };
        return { eventId, ...rest };
      }) ?? [];
    })
    .flat()
    .filter(special => {
      if (special.name != 'Winning Margin' && special.name != 'Exact Total Goals') {
        return false;
      }
      return true;
    }) ?? [];
    const update = since == 0 ? 'full' : 'increment';
    return { specials, update };
  });
}


const mergeContestants = (localContestants=[], remoteContestants=[]) => {
  const localContestantsMap = new Map(localContestants.map(contestant => [contestant.id, contestant]));
  remoteContestants.forEach(contestant => {
    const localContestant = localContestantsMap.get(contestant.id);
    if (localContestant) {
      Object.assign(localContestant, contestant);
    }
    else {
      localContestants.push(contestant);
    }
  });
  const remoteContestantsMap = new Map(remoteContestants.map(contestant => [contestant.id, contestant]));
  for (let i = localContestants.length - 1; i >= 0; i--) {
    if (!remoteContestantsMap.has(localContestants[i].id)) {
      localContestants.splice(i, 1);
    }
  }
  return localContestants;
}

const updateSpecialFixtures = async () => {
  return getSpecialFixtures()
  .then(data => {
    const { specials, update } = data;
    if (specials?.length) {
      const { gamesMap } = GLOBAL_DATA;
      const gamesSpecialsMap = {};
      specials.forEach(special => {
        const { eventId } = special;
        if (!gamesSpecialsMap[eventId]) {
          gamesSpecialsMap[eventId] = {};
        }
        if (special.name == 'Winning Margin') {
          gamesSpecialsMap[eventId].winningMargin = special;
        }
        else if (special.name == 'Exact Total Goals') {
          gamesSpecialsMap[eventId].exactTotalGoals = special;
        }
      });

      Object.keys(gamesSpecialsMap).forEach(eventId => {
        if (!gamesMap[eventId]) {
          return;
        }

        if (!gamesMap[eventId].specials) {
          gamesMap[eventId].specials = {};
        }

        const localSpecials = gamesMap[eventId].specials;
        const remoteSpecials = gamesSpecialsMap[eventId];

        if (localSpecials.winningMargin && remoteSpecials.winningMargin) {
          const { contestants: winningMarginContestants, ...winningMarginRest } = remoteSpecials.winningMargin;
          Object.assign(localSpecials.winningMargin, winningMarginRest);
          mergeContestants(localSpecials.winningMargin.contestants, winningMarginContestants);
        }
        else if (localSpecials.winningMargin && !remoteSpecials.winningMargin) {
          Logs.outDev('delete winningMargin', localSpecials.winningMargin);
          delete localSpecials.winningMargin;
        }
        else if (remoteSpecials.winningMargin) {
          localSpecials.winningMargin = remoteSpecials.winningMargin;
        }

        if (localSpecials.exactTotalGoals && remoteSpecials.exactTotalGoals) {
          const { contestants: exactTotalGoalsContestants, ...exactTotalGoalsRest } = remoteSpecials.exactTotalGoals;
          Object.assign(localSpecials.exactTotalGoals, exactTotalGoalsRest);
          mergeContestants(localSpecials.exactTotalGoals.contestants, exactTotalGoalsContestants);
        }
        else if (localSpecials.exactTotalGoals && !remoteSpecials.exactTotalGoals) {
          Logs.outDev('delete exactTotalGoals', localSpecials.exactTotalGoals);
          delete localSpecials.exactTotalGoals;
        }
        else if (remoteSpecials.exactTotalGoals) {
          localSpecials.exactTotalGoals = remoteSpecials.exactTotalGoals;
        }
      });
    }
  });
}


const getSpecialsOdds = async () => {
  if (!GLOBAL_DATA.filtedLeagues.length) {
    return Promise.reject(new Error('no filted leagues'));
  }
  const leagueIds = GLOBAL_DATA.filtedLeagues.join(',');
  const since = GLOBAL_DATA.specialsOddsVersion;
  // if (GLOBAL_DATA.specialsOddsCount >= 33) {
  //   since = 0;
  //   GLOBAL_DATA.specialsOddsCount = 9;
  // }
  if (since == 0) {
    Logs.outDev('full update specials odds');
  }
  return pinnacleGet('/v2/odds/special', { sportId: 29, oddsFormat: 'Decimal', leagueIds, since })
  .then(data => {
    const { leagues, last } = data;
    if (!last) {
      return [];
    }
    GLOBAL_DATA.specialsOddsVersion = last;
    return leagues?.flatMap(league => league.specials);
  });
}


const updateSpecialsOdds = async () => {
  return getSpecialsOdds()
  .then(specials => {
    if (specials.length) {
      const { gamesMap } = GLOBAL_DATA;
      const contestants = Object.values(gamesMap)
      .filter(game => game.specials)
      .map(game => {
        const { specials } = game;
        const contestants = Object.values(specials).map(special => {
          const { contestants } = special;
          return contestants.map(contestant => [contestant.id, contestant]);
        });
        return contestants;
      }).flat(2);
      const contestantsMap = new Map(contestants);
      const lines = specials.flatMap(special => special.contestantLines);
      lines.forEach(line => {
        const { id, handicap, lineId, max, price } = line;
        const contestant = contestantsMap.get(id);
        if (!contestant) {
          return;
        }
        contestant.handicap = handicap;
        contestant.lineId = lineId;
        contestant.max = max;
        contestant.price = price;
      });
    }
  });
}


const getInRunning = async () => {
  return pinnacleGet('/v2/inrunning')
  .then(data => {
    const sportId = 29;
    const leagues = data.sports?.find(sport => sport.id == sportId)?.leagues ?? [];
    return leagues.filter(league => {
      const { id } = league;
      const filtedLeaguesSet = new Set(GLOBAL_DATA.filtedLeagues);
      return filtedLeaguesSet.has(id);
    }).flatMap(league => league.events);
  });
}


const updateInRunning = async () => {
  return getInRunning()
  .then(games => {
    if (!games.length) {
      return;
    }
    const { gamesMap } = GLOBAL_DATA;
    games.forEach(game => {
      const { id, state, elapsed } = game;
      const localGame = gamesMap[id];
      if (localGame) {
        Object.assign(localGame, { state, elapsed });
      }
    });
  });
}


const ratioAccept = (ratio) => {
  if (ratio > 0) {
    return 'a';
  }
  return '';
}
const ratioString = (ratio) => {
  ratio = Math.abs(ratio);
  ratio = ratio.toString();
  ratio = ratio.replace(/\./, '');
  return ratio;
}

const parseSpreads = (spreads, wm) => {
  // 让分盘
  if (!spreads?.length) {
    return null;
  }
  const events = {};
  spreads.forEach(spread => {
    const { hdp, home, away } = spread;

    // if (!(hdp % 1) || !!(hdp % 0.5)) {
    //   // 整数或不能被0.5整除的让分盘不处理
    //   return;
    // }

    const ratio_ro = hdp;
    const ratio_r = ratio_ro - wm;
    events[`ior_r${ratioAccept(ratio_r)}h_${ratioString(ratio_r)}`] = {
      v: home,
      r: wm != 0 ? `ior_r${ratioAccept(ratio_ro)}h_${ratioString(ratio_ro)}` : undefined
    };
    events[`ior_r${ratioAccept(-ratio_r)}c_${ratioString(ratio_r)}`] = {
      v: away,
      r: wm != 0 ? `ior_r${ratioAccept(-ratio_ro)}c_${ratioString(ratio_ro)}` : undefined
    };
  });
  return events;
}

const parseMoneyline = (moneyline) => {
  // 胜平负
  if (!moneyline) {
    return null;
  }
  const { home, away, draw } = moneyline;
  return {
    'ior_mh': { v: home },
    'ior_mc': { v: away },
    'ior_mn': { v: draw },
  }
}

const parseTotals = (totals) => {
  // 大小球盘
  if (!totals?.length) {
    return null;
  }
  const events = {};

  totals.forEach(total => {
    const { points, over, under } = total;
    events[`ior_ouc_${ratioString(points)}`] = { v: over };
    events[`ior_ouh_${ratioString(points)}`] = { v: under };
  });
  return events;
}

const parsePeriod = (period, wm) => {
  const { cutoff='', status=0, spreads=[], moneyline={}, totals=[] } = period;
  const cutoffTime = new Date(cutoff).getTime();
  const nowTime = Date.now();

  if (status != 1 || cutoffTime < nowTime) {
    return null;
  }

  const events = {};
  Object.assign(events, parseSpreads(spreads, wm));
  Object.assign(events, parseMoneyline(moneyline));
  Object.assign(events, parseTotals(totals));

  return events;
}

const parseWinningMargin = (winningMargin, home, away) => {
  const { cutoff='', status='', contestants=[] } = winningMargin;
  const cutoffTime = new Date(cutoff).getTime();
  const nowTime = Date.now();

  if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
    return null;
  }

  const events = {};
  contestants.forEach(contestant => {
    const { name, price } = contestant;
    const nr = name.match(/\d+$/)?.[0];
    if (!nr) {
      return;
    }
    let side;
    if (name.startsWith(home)) {
      side = 'h';
    }
    else if (name.startsWith(away)) {
      side = 'c';
    }
    else {
      return;
    }
    events[`ior_wm${side}_${nr}`] = { v: price };
  });
  return events;
}

const parseExactTotalGoals = (exactTotalGoals) => {
  const { cutoff='', status='', contestants=[] } = exactTotalGoals;
  const cutoffTime = new Date(cutoff).getTime();
  const nowTime = Date.now();

  if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
    return null;
  }

  const events = {};
  contestants.forEach(contestant => {
    const { name, price } = contestant;
    if (+name >= 1 && +name <= 7) {
      events[`ior_ot_${name}`] = { v: price };
    }
  });
  return events;
}

const parseRbState = (state) => {
  let stage = null;
  if (state == 1) {
    stage = '1H';
  }
  else if (state == 2) {
    stage = 'HT';
  }
  else if (state == 3) {
    stage = '2H';
  }
  else if (state >= 4) {
    stage = 'ET';
  }
  return stage;
}

const parseGame = (game) => {
  const { eventId=0, originId=0, period={}, specials={}, home, away, marketType, state, elapsed, homeScore=0, awayScore=0 } = game;
  const { winningMargin={}, exactTotalGoals={} } = specials;
  const wm = homeScore - awayScore;
  const score = `${homeScore}-${awayScore}`;
  const events = parsePeriod(period, wm) ?? {};
  const stage = parseRbState(state);
  const retime = elapsed ? `${elapsed}'` : '';
  const evtime = Date.now();
  Object.assign(events, parseWinningMargin(winningMargin, home, away));
  Object.assign(events, parseExactTotalGoals(exactTotalGoals));
  return { eventId, originId, events, evtime, stage, retime, score, wm, marketType };
}


const getGames = () => {
  const { filtedGames, gamesMap } = GLOBAL_DATA;
  const filtedGamesSet = new Set(filtedGames);
  const nowTime = Date.now();
  const gamesData = {};
  Object.values(gamesMap).forEach(game => {
    const { id, liveStatus, parentId, resultingUnit, timestamp } = game;

    if (resultingUnit !== 'Regular') {
      return false;  // 非常规赛事不处理
    }

    const gmtMinus4Date = getDateInTimezone(-4);
    const todayEndTime = new Date(`${gmtMinus4Date} 23:59:59 GMT-4`).getTime();
    const tomorrowEndTime = todayEndTime + 24 * 60 * 60 * 1000;
    if (liveStatus == 1 && timestamp < nowTime) {
      game.marketType = 2;  // 滚球赛事
    }
    else if (liveStatus != 1 && timestamp > nowTime && timestamp <= todayEndTime) {
      game.marketType = 1;  // 今日赛事
    }
    else if (liveStatus != 1 && timestamp > todayEndTime && timestamp <= tomorrowEndTime) {
      game.marketType = 0;  // 明日早盘赛事
    }
    else {
      game.marketType = -1;  // 非近期赛事
    }

    if (game.marketType < 0) {
      return false;  // 非近期赛事不处理
    }

    let actived = false;
    if (liveStatus != 1 && filtedGamesSet.has(id)) {
      actived = true;  // 在赛前列表中
      game.eventId = id;
      game.originId = 0;
    }
    else if (liveStatus == 1 && filtedGamesSet.has(parentId)) {
      actived = true;  // 在滚球列表中
      game.eventId = parentId;
      game.originId = id;
    }

    if (actived) {
      const gameInfo = parseGame(game);
      const { marketType, ...rest } = gameInfo;
      if (!gamesData[marketType]) {
        gamesData[marketType] = [];
      }
      gamesData[marketType].push(rest);
    }
  });
  return gamesData;
}


const pinnacleDataLoop = () => {
  updateStraightFixtures()
  .then(() => {
    return Promise.all([
      updateStraightOdds(),
      updateSpecialFixtures(),
      updateInRunning(),
    ]);
  })
  .then(() => {
    return updateSpecialsOdds();
  })
  .then(() => {
    if (!GLOBAL_DATA.loopActive) {
      GLOBAL_DATA.loopActive = true;
      Logs.out('loop active');
      notifyException('Pinnacle API startup.');
    }

    if (GLOBAL_DATA.requestErrorCount > 0) {
      GLOBAL_DATA.requestErrorCount = 0;
      Logs.out('request error count reset');
    }

    const nowTime = Date.now();
    const loopDuration = nowTime - GLOBAL_DATA.loopResultTime;
    GLOBAL_DATA.loopResultTime = nowTime;
    if (loopDuration > 15000) {
      Logs.out('loop duration is too long', loopDuration);
    }
    else {
      Logs.outDev('loop duration', loopDuration);
    }

    const { straightFixturesVersion: sfv, specialFixturesVersion: pfv, straightOddsVersion: sov, specialsOddsVersion: pov } = GLOBAL_DATA;
    const timestamp = Math.max(sfv, pfv, sov, pov);
    const games = getGames();
    const data = { games, timestamp, tp: TP };

    updateBaseEvents(data);

    if (IS_DEV) {
      setData(gamesMapCacheFile, GLOBAL_DATA.gamesMap)
      .then(() => {
        Logs.outDev('games map saved');
      })
      .catch(err => {
        Logs.err('failed to save games map', err.message);
      });
    }

  })
  .catch(err => {
    Logs.err(err.message, err.source);
    GLOBAL_DATA.requestErrorCount++;
    if (GLOBAL_DATA.loopActive && GLOBAL_DATA.requestErrorCount > 5) {
      const exceptionMessage = 'request errors have reached the limit';
      Logs.out(exceptionMessage);
      GLOBAL_DATA.loopActive = false;

      Logs.out('loop inactive');
      notifyException(`Pinnacle API paused. ${exceptionMessage}. ${err.message}`);
    }
  })
  .finally(() => {
    const { loopActive } = GLOBAL_DATA;
    let loopDelay = 1000 * 5;
    if (!loopActive) {
      loopDelay = 1000 * 60;
      resetVersionsCount();
    }
    else {
      incrementVersionsCount();
    }
    setTimeout(pinnacleDataLoop, loopDelay);
  });
}


/**
 * 缓存GLOBAL_DATA数据到文件
 */
const saveGlobalDataToCache = async () => {
  return setData(globalDataCacheFile, GLOBAL_DATA);
}

const loadGlobalDataFromCache = async () => {
  return getData(globalDataCacheFile)
  .then(data => {
    if (!data) {
      return;
    }
    Object.keys(GLOBAL_DATA).forEach(key => {
      if (key in data) {
        GLOBAL_DATA[key] = data[key];
      }
    });
  });
}

// 监听进程退出事件，保存GLOBAL_DATA数据
const saveExit = (code) => {
  saveGlobalDataToCache()
  .then(() => {
    Logs.out('global data saved');
  })
  .catch(err => {
    Logs.err('failed to save global data', err.message);
  })
  .finally(() => {
    process.exit(code);
  });
}
process.on('SIGINT', () => {
  saveExit(0);
});
process.on('SIGTERM', () => {
  saveExit(0);
});
process.on('SIGUSR2', () => {
  saveExit(0);
});


(() => {
  if (!process.env.PINNACLE_USERNAME || !process.env.PINNACLE_PASSWORD) {
    Logs.err('USERNAME or PASSWORD is not set');
    return;
  }
  loadGlobalDataFromCache()
  .then(() => {
    Logs.out('global data loaded');
  })
  .catch(err => {
    Logs.err('failed to load global data', err.message);
  })
  .finally(() => {
    GLOBAL_DATA.loopResultTime = Date.now();
    GLOBAL_DATA.loopActive = true;
    return getFiltedGames();
  })
  .then(pinnacleDataLoop);
})();


