/**
 * News Service
 * Aggregates financial news from free sources:
 * - Yahoo Finance RSS per ticker
 * - Google News RSS (filtered to financial keywords)
 * - Finviz news table scraper
 * - Seeking Alpha RSS
 */

import NodeCache from 'node-cache';
import { parseStringPromise } from 'xml2js';

const newsCache = new NodeCache({ stdTTL: 300 });  // 5min cache

const FINANCE_KEYWORDS = [
  'earnings', 'revenue', 'profit', 'loss', 'EPS', 'guidance', 'forecast',
  'analyst', 'upgrade', 'downgrade', 'buy', 'sell', 'hold', 'target',
  'merger', 'acquisition', 'buyback', 'dividend', 'split', 'IPO', 'offering',
  'FDA', 'approval', 'patent', 'lawsuit', 'SEC', 'investigation',
  'Fed', 'rate', 'inflation', 'GDP', 'market', 'stock', 'shares',
  'quarter', 'annual', 'fiscal', 'outlook', 'beat', 'miss', 'exceed',
  'short', 'hedge', 'fund', 'institutional', 'investment', 'investor',
  'growth', 'decline', 'surge', 'drop', 'rally', 'correction', 'pullback'
];

const DEFAULT_HEADERS = {
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Accept': 'application/rss+xml, application/xml, text/xml, */*',
};

async function fetchRSS(url, timeout = 8000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);
  try {
    const res = await fetch(url, { headers: DEFAULT_HEADERS, signal: controller.signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const text = await res.text();
    return parseStringPromise(text, { explicitArray: false, ignoreAttrs: false });
  } finally {
    clearTimeout(timer);
  }
}

/**
 * Extract items from parsed RSS feed
 */
function extractRSSItems(parsed, source) {
  try {
    const items = parsed?.rss?.channel?.item || parsed?.feed?.entry || [];
    const list = Array.isArray(items) ? items : [items];
    return list.map(item => ({
      title: item.title?._ || item.title || '',
      url: item.link?.href || item.link || item.guid?._ || item.guid || '',
      publishedAt: item.pubDate || item.published || item.updated || '',
      source,
      snippet: item.description?._ || item.description || item.summary?._ || item.summary || '',
    })).filter(i => i.title);
  } catch {
    return [];
  }
}

/**
 * Filter to only financial/business relevant articles
 */
function isFinancialNews(item) {
  const text = `${item.title} ${item.snippet}`.toLowerCase();
  return FINANCE_KEYWORDS.some(kw => text.includes(kw.toLowerCase()));
}

/**
 * Scrape news from Finviz (no API key needed)
 */
async function fetchFinvizNews(symbol, timeout = 8000) {
  const url = `https://finviz.com/quote.ashx?t=${symbol}`;
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);
  const items = [];

  try {
    const res = await fetch(url, {
      headers: {
        ...DEFAULT_HEADERS,
        'Accept': 'text/html,application/xhtml+xml',
      },
      signal: controller.signal
    });

    if (!res.ok) return [];
    const html = await res.text();

    // Extract news table rows using regex (no DOM parser needed)
    // Finviz news rows look like: <tr class="cursor-pointer"><td ...><a href="..." class="tab-link">TITLE</a>...DATE</td>
    const newsRowRegex = /<a[^>]+class="[^"]*tab-link[^"]*"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi;
    const dateRegex = /<td[^>]*class="[^"]*news-date[^"]*"[^>]*>([^<]+)<\/td>/gi;

    let match;
    const dates = [];
    while ((match = dateRegex.exec(html)) !== null) {
      dates.push(match[1].trim());
    }

    let linkIdx = 0;
    while ((match = newsRowRegex.exec(html)) !== null) {
      const href = match[1];
      const title = match[2].trim();
      if (title && href && !href.startsWith('/') && title.length > 10) {
        items.push({
          title,
          url: href,
          publishedAt: dates[linkIdx] || '',
          source: 'Finviz',
          snippet: '',
        });
        linkIdx++;
      }
    }

    return items.slice(0, 15);
  } catch {
    return [];
  } finally {
    clearTimeout(timer);
  }
}

/**
 * Assign basic sentiment based on keywords
 */
function assignSentiment(title) {
  const text = title.toLowerCase();
  const bullish = ['beat', 'surge', 'rally', 'upgrade', 'buy', 'strong', 'growth', 'profit', 'record', 'high', 'exceed', 'outperform', 'dividend', 'buyback'];
  const bearish = ['miss', 'drop', 'fall', 'downgrade', 'sell', 'weak', 'loss', 'cut', 'warning', 'investigation', 'lawsuit', 'layoff', 'short', 'concern'];

  const bullScore = bullish.filter(w => text.includes(w)).length;
  const bearScore = bearish.filter(w => text.includes(w)).length;

  if (bullScore > bearScore) return 'BULLISH';
  if (bearScore > bullScore) return 'BEARISH';
  return 'NEUTRAL';
}

/**
 * Fetch all news for a symbol from multiple free sources
 */
export async function fetchNewsForSymbol(symbol) {
  const cacheKey = `news_${symbol}`;
  const cached = newsCache.get(cacheKey);
  if (cached) return cached;

  const sources = [
    {
      name: 'Yahoo Finance',
      fetch: () => fetchRSS(`https://finance.yahoo.com/rss/headline?s=${symbol}`),
    },
    {
      name: 'Google News',
      fetch: () => fetchRSS(`https://news.google.com/rss/search?q=${symbol}+stock+market&hl=en-US&gl=US&ceid=US:en`),
    },
    {
      name: 'Seeking Alpha',
      fetch: () => fetchRSS(`https://seekingalpha.com/api/sa/combined/${symbol}.xml`),
    },
  ];

  const results = await Promise.allSettled([
    ...sources.map(s => s.fetch()),
    fetchFinvizNews(symbol),
  ]);

  let allItems = [];

  // Process RSS sources
  results.slice(0, sources.length).forEach((res, i) => {
    if (res.status === 'fulfilled' && res.value) {
      const items = extractRSSItems(res.value, sources[i].name);
      allItems.push(...items);
    }
  });

  // Process Finviz
  const finvizResult = results[sources.length];
  if (finvizResult.status === 'fulfilled') {
    allItems.push(...(finvizResult.value || []));
  }

  // Filter to financial news only (except Yahoo Finance — already financial)
  allItems = allItems.filter(item => {
    if (item.source === 'Yahoo Finance' || item.source === 'Finviz') return true;
    return isFinancialNews(item);
  });

  // Deduplicate by title similarity
  const seen = new Set();
  allItems = allItems.filter(item => {
    const key = item.title.slice(0, 60).toLowerCase().replace(/[^a-z0-9]/g, '');
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });

  // Add sentiment and sort
  allItems = allItems.map(item => ({
    ...item,
    sentiment: assignSentiment(item.title),
    symbol: symbol.toUpperCase(),
  }));

  // Sort by date (most recent first) — handle mixed date formats
  allItems.sort((a, b) => {
    try {
      return new Date(b.publishedAt) - new Date(a.publishedAt);
    } catch { return 0; }
  });

  const finalItems = allItems.slice(0, 25);
  newsCache.set(cacheKey, finalItems);
  return finalItems;
}

/**
 * Fetch market-wide news (SPY, QQQ, broader market)
 */
export async function fetchMarketNews() {
  const cacheKey = 'market_news';
  const cached = newsCache.get(cacheKey);
  if (cached) return cached;

  try {
    const [spyNews, marketRSS] = await Promise.allSettled([
      fetchNewsForSymbol('SPY'),
      fetchRSS('https://feeds.finance.yahoo.com/rss/2.0/headline?s=SPY,QQQ,^GSPC&region=US&lang=en-US'),
    ]);

    const items = [
      ...(spyNews.status === 'fulfilled' ? spyNews.value : []),
    ].slice(0, 20);

    newsCache.set(cacheKey, items);
    return items;
  } catch {
    return [];
  }
}
