import express from 'express';

import { getCurrenciesInfo, getEventsByIds } from '../libs/globalData.js';
import { pinnacleGet, pinnaclePost } from '../libs/pinnacleClient.js';

const router = express.Router();
const SECOND = 1000;
const MINUTE = 60 * SECOND;

const getCacheTtls = new Map([
  ['/v3/sports', MINUTE],
  ['/v2/inrunning', 2 * SECOND],
  ['/v1/bets/betting-status', SECOND],
  ['/v1/cancellationreasons', 10 * MINUTE],
]);

const getRateLimitTtls = new Map([
  ['/v3/leagues', MINUTE],
  ['/v3/bets', 2 * SECOND],
]);

const responseCache = new Map();
const inFlightRequests = new Map();
const lastRequests = new Map();

const allowedEndpoints = new Map([
  ['GET /v2/line', 'Get Straight Line - v2'],
  ['POST /v3/line/parlay', 'Get Parlay Line - v3'],
  ['POST /v1/line/teaser', 'Get Teaser Line - v1'],
  ['GET /v2/line/special', 'Get Special Line - v2'],
  ['GET /v3/sports', 'Get Sports - v3'],
  ['GET /v3/leagues', 'Get Leagues - v3'],
  ['GET /v1/periods', 'Get Periods - v1'],
  ['GET /v2/inrunning', 'Get In-Running - v2'],
  ['GET /v1/cancellationreasons', 'Get Cancellation Reasons - v1'],
  ['GET /v2/currencies', 'Get Currencies - v2'],
  ['GET /v1/client/balance', 'Get Client Balance - v1'],
  ['POST /v4/bets/place', 'Place straight bet - v4'],
  ['POST /v4/bets/parlay', 'Place parlay bet - v4'],
  ['POST /v4/bets/teaser', 'Place teaser bet - v4'],
  ['POST /v4/bets/special', 'Place specials bet - v4'],
  ['GET /v3/bets', 'Get Bets - v3'],
  ['GET /v3/bets/settled', 'Get Bets Settled - v3'],
  ['GET /v1/regrades/wager-history', 'Get Regrades Wager History - v1'],
  ['GET /v1/bets/betting-status', 'Get Betting Status'],
]);

const normalizePath = (path) => {
  if (!path || path === '/') {
    return '/';
  }
  return path.endsWith('/') ? path.slice(0, -1) : path;
};

const stableStringify = (value) => {
  if (Array.isArray(value)) {
    return `[${value.map(stableStringify).join(',')}]`;
  }

  if (value && typeof value === 'object') {
    return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
  }

  return JSON.stringify(value);
};

const getCacheKey = (method, targetPath, query) => `${method} ${targetPath} ${stableStringify(query)}`;

const checkRateLimit = (targetPath) => {
  const ttl = getRateLimitTtls.get(targetPath);
  if (!ttl) {
    return null;
  }

  const lastRequestAt = lastRequests.get(targetPath) ?? 0;
  const retryAfterMs = lastRequestAt + ttl - Date.now();
  if (retryAfterMs > 0) {
    return retryAfterMs;
  }

  lastRequests.set(targetPath, Date.now());
  return null;
};

const getCachedPinnacleData = async (targetPath, query) => {
  if (targetPath === '/v2/currencies') {
    const { data, updatedAt } = await getCurrenciesInfo();
    return { cacheStatus: 'GLOBAL_DATA', data, updatedAt };
  }

  const ttl = getCacheTtls.get(targetPath);

  if (!ttl) {
    return { cacheStatus: 'BYPASS', data: await pinnacleGet(targetPath, query) };
  }

  const cacheKey = getCacheKey('GET', targetPath, query);
  const cached = responseCache.get(cacheKey);
  if (cached && Date.now() - cached.createdAt < ttl) {
    return { cacheStatus: 'HIT', data: cached.data };
  }

  if (inFlightRequests.has(cacheKey)) {
    return { cacheStatus: 'INFLIGHT', data: await inFlightRequests.get(cacheKey) };
  }

  const request = pinnacleGet(targetPath, query)
  .then(data => {
    responseCache.set(cacheKey, { createdAt: Date.now(), data });
    return data;
  })
  .finally(() => {
    inFlightRequests.delete(cacheKey);
  });

  inFlightRequests.set(cacheKey, request);
  return { cacheStatus: cached ? 'STALE_REFRESH' : 'MISS', data: await request };
};

const getEventIdsFromRequest = (req) => {
  const source = req.method === 'GET' ? req.query : req.body;
  return source?.ids ?? source?.id ?? source?.eventIds ?? source?.eventId;
};

router.use(async (req, res) => {
  const method = req.method.toUpperCase();
  const targetPath = normalizePath(req.path);

  if (targetPath === '/events' && (method === 'GET' || method === 'POST')) {
    const ids = getEventIdsFromRequest(req);
    return res.status(200).json(getEventsByIds(ids));
  }

  const endpointKey = `${method} ${targetPath}`;

  if (!allowedEndpoints.has(endpointKey)) {
    return res.status(404).json({
      statusCode: 404,
      code: -1,
      message: 'Unsupported Pinnacle API endpoint',
      data: { method, path: targetPath },
    });
  }

  try {
    if (method === 'GET') {
      const retryAfterMs = checkRateLimit(targetPath);
      if (retryAfterMs) {
        res.set('Retry-After', String(Math.ceil(retryAfterMs / SECOND)));
        return res.status(429).json({
          statusCode: 429,
          code: -1,
          message: 'Pinnacle API endpoint is rate limited, please retry later',
          data: {
            path: targetPath,
            retryAfterMs,
          },
        });
      }
    }

    if (method === 'GET') {
      const data = await getCachedPinnacleData(targetPath, req.query);
      res.set('X-Pinnacle-Proxy-Cache', data.cacheStatus);
      if (data.updatedAt) {
        res.set('X-Pinnacle-Proxy-Updated-At', String(data.updatedAt));
      }
      return res.status(200).json(data.data);
    }

    const data = await pinnaclePost(targetPath, req.body);
    return res.status(200).json(data);
  }
  catch (err) {
    const status = err.response?.status ?? err.status ?? err.cause ?? 500;
    const data = err.response?.data ?? err.data;
    const message = data?.message ?? err.message ?? 'Pinnacle API request failed';

    return res.status(status).json(data ?? {
      statusCode: status,
      code: -1,
      message,
    });
  }
});

export default router;
