'use strict';

/**
 * newsService — the FREE ESPN news wire (Wave 2A / offseason-hub).
 *
 * Mirrors injuryService's pattern EXACTLY: per-sport FEEDS map → ESPN's
 * free `/news` endpoint, injectable axios/http + cache 15 min, pure
 * `parseNews(json)` → the Wave-2 news contract. NO odds-api credits.
 *
 * Contract (consumed by the parallel Wave 2B ExploreHub):
 *   { sport, items: [{ id, headline, description, published (ISO), type,
 *                      athlete?: { name, key }, team?: string, href }] }
 * Newest first, cap ~25. Empty feed → { sport, items: [] } (never errors).
 *
 * Governing rules: never fabricate — athlete/team are extracted ONLY from
 * ESPN `categories[]` where present, and are ABSENT (undefined) otherwise.
 */

const { nameKey } = require('../utils/playerName');

const TTL = 900; // 15 min — same cadence as injuryService.
const HTTP_TIMEOUT_MS = 10_000;
const CAP = 25;

// ESPN site API `/news` per sport (FREE, no auth). Soccer/nfl/nhl are
// optional — absent keys degrade to an empty feed, never an error.
const FEEDS = {
  mlb: 'https://site.api.espn.com/apis/site/v2/sports/baseball/mlb/news',
  nba: 'https://site.api.espn.com/apis/site/v2/sports/basketball/nba/news',
  wnba: 'https://site.api.espn.com/apis/site/v2/sports/basketball/wnba/news',
  nfl: 'https://site.api.espn.com/apis/site/v2/sports/football/nfl/news',
  nhl: 'https://site.api.espn.com/apis/site/v2/sports/hockey/nhl/news',
};

/** First `categories[]` entry of a given ESPN type, or null. */
function firstCategory(categories, type) {
  for (const c of Array.isArray(categories) ? categories : []) {
    if (c && c.type === type) return c;
  }
  return null;
}

/**
 * Pure: one ESPN article → the contract's item, or null if unusable
 * (no headline). athlete/team are attached ONLY when a matching category
 * is present — never invented.
 */
function parseArticle(article) {
  if (!article || !article.headline) return null;

  const cats = article.categories;
  const item = {
    id: article.id != null ? String(article.id) : null,
    headline: String(article.headline),
    description: article.description ? String(article.description) : '',
    published: article.published || article.lastModified || null,
    type: article.type || 'Story',
    href: (article.links && article.links.web && article.links.web.href) || null,
  };

  // Athlete — from the first `type:'athlete'` category (has `description` = name).
  const ath = firstCategory(cats, 'athlete');
  const athName = ath && (ath.description || (ath.athlete && ath.athlete.description));
  if (athName) {
    item.athlete = { name: String(athName), key: nameKey(String(athName)) };
  }

  // Team — from the first `type:'team'` category (full display name, or abbr).
  const tm = firstCategory(cats, 'team');
  const tmName = tm && (tm.description || (tm.team && (tm.team.description || tm.team.abbreviation)));
  if (tmName) {
    item.team = String(tmName);
  }

  return item;
}

/** Pure: ESPN news JSON → the contract's items[] (newest first, capped). */
function parseNews(json) {
  const articles = (json && Array.isArray(json.articles)) ? json.articles : [];
  const items = [];
  for (const a of articles) {
    const it = parseArticle(a);
    if (it) items.push(it);
  }
  // ESPN returns newest-first already; sort defensively by `published` desc
  // (items with no timestamp sink to the bottom, order preserved otherwise).
  items.sort((x, y) => {
    const tx = x.published ? Date.parse(x.published) : 0;
    const ty = y.published ? Date.parse(y.published) : 0;
    return (ty || 0) - (tx || 0);
  });
  return items.slice(0, CAP);
}

/**
 * Fetch (cache-aside) the news wire for a sport → { sport, items: [] }.
 * Injectable axios/cacheGet/cacheSet → unit-tested fully offline. Never
 * throws: an unknown sport or a failed fetch returns an empty feed.
 */
async function getNews(sport, opts = {}) {
  const sp = String(sport || '').toLowerCase();
  const url = FEEDS[sp];
  if (!url) return { sport: sp, items: [] };

  const cacheGet = opts.cacheGet || require('../utils/redis').cacheGet;
  const cacheSet = opts.cacheSet || require('../utils/redis').cacheSet;
  const key = `news:${sp}`;

  try {
    const cached = await cacheGet(key);
    if (cached && Array.isArray(cached.items)) return { sport: sp, items: cached.items };
  } catch (_) { /* cache miss / redis down → fetch live */ }

  const axios = opts.axios || require('axios');
  try {
    const res = await axios.get(url, { timeout: HTTP_TIMEOUT_MS });
    const items = parseNews(res.data);
    try { await cacheSet(key, { items }, TTL); } catch (_) { /* best-effort */ }
    return { sport: sp, items };
  } catch (e) {
    console.warn(`[news] ${sp} fetch failed:`, e.message);
    return { sport: sp, items: [] };
  }
}

module.exports = { getNews, parseNews, parseArticle, __internals: { TTL, FEEDS, CAP } };
