/**
 * ARCH-2 (Session 7e): LEGACY circuit breaker. The canonical implementation
 * lives in src/utils/rateLimiter.js (createCircuitBreaker factory).
 * New code should import from there.
 *
 * Current callers of this legacy module (remove this file when the list
 * empties):
 *   - src/services/UnifiedOddsProvider.js
 *   - src/services/adapters/ESPNAdapter.js
 *   - src/services/adapters/PinnacleAdapter.js
 *
 * Per-upstream circuit breaker.
 *
 * States:
 *   CLOSED   — calls flow normally
 *   OPEN     — calls short-circuit instantly with BreakerOpenError
 *   HALF_OPEN — one trial call allowed; success closes, failure re-opens
 *
 * Tunable per key; defaults are conservative (open after 5 fails in 60s,
 * stay open for 30s). Set per-source thresholds in OVERRIDES.
 */

const STATES = Object.freeze({ CLOSED: 'CLOSED', OPEN: 'OPEN', HALF_OPEN: 'HALF_OPEN' });

const DEFAULTS = {
  failureThreshold: 5,
  windowMs: 60_000,
  cooldownMs: 30_000,
};

const OVERRIDES = Object.freeze({
  pinnacle: { failureThreshold: 3, cooldownMs: 60_000 },
  'nba-stats': { failureThreshold: 4, cooldownMs: 45_000 },
  pybaseball: { failureThreshold: 3, cooldownMs: 60_000 },
});

class BreakerOpenError extends Error {
  constructor(key, retryAt) {
    super(`circuit open for ${key}`);
    this.name = 'BreakerOpenError';
    this.code = 'BREAKER_OPEN';
    this.upstream = key;
    this.retryAt = retryAt;
  }
}

const breakers = new Map();

function getBreaker(key) {
  let b = breakers.get(key);
  if (!b) {
    const cfg = { ...DEFAULTS, ...(OVERRIDES[key] || {}) };
    b = {
      key,
      state: STATES.CLOSED,
      failures: [],
      openedAt: 0,
      cfg,
    };
    breakers.set(key, b);
  }
  return b;
}

function pruneFailures(b, now) {
  const cutoff = now - b.cfg.windowMs;
  while (b.failures.length && b.failures[0] < cutoff) b.failures.shift();
}

/**
 * Run `fn` under the breaker. If the breaker is OPEN, throws immediately.
 * Counts thrown errors as failures, except those marked `err.skipBreaker`.
 */
async function call(key, fn) {
  const b = getBreaker(key);
  const now = Date.now();

  if (b.state === STATES.OPEN) {
    const reopenAt = b.openedAt + b.cfg.cooldownMs;
    if (now < reopenAt) throw new BreakerOpenError(key, reopenAt);
    // Cooldown elapsed — give one trial call.
    b.state = STATES.HALF_OPEN;
  }

  try {
    const result = await fn();
    if (b.state === STATES.HALF_OPEN) {
      b.state = STATES.CLOSED;
      b.failures.length = 0;
    }
    return result;
  } catch (err) {
    if (!err || !err.skipBreaker) {
      b.failures.push(Date.now());
      pruneFailures(b, Date.now());
      if (b.state === STATES.HALF_OPEN || b.failures.length >= b.cfg.failureThreshold) {
        b.state = STATES.OPEN;
        b.openedAt = Date.now();
      }
    }
    throw err;
  }
}

function snapshot() {
  const out = {};
  for (const [k, b] of breakers.entries()) {
    pruneFailures(b, Date.now());
    out[k] = {
      state: b.state,
      failures: b.failures.length,
      cooldownEndsAt: b.state === STATES.OPEN ? b.openedAt + b.cfg.cooldownMs : null,
    };
  }
  return out;
}

function reset(key) {
  if (key) breakers.delete(key);
  else breakers.clear();
}

module.exports = { call, snapshot, reset, BreakerOpenError, STATES };
