const axios = require('axios');
const NodeCache = require('node-cache');
const { pool } = require('./db');
const http = require('http');
const https = require('https');

// Cache for 90 seconds to reduce API calls and improve throughput
const cache = new NodeCache({ stdTTL: 90, checkperiod: 120 });

// HTTP keep-alive agent for connection pooling to external APIs
const httpAgent = new http.Agent({
  keepAlive: true,
  keepAliveMsecs: 30000,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000,
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  keepAliveMsecs: 30000,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000,
});

// Popular stock symbols to track
const DEFAULT_SYMBOLS = [
  'AAPL',
  'GOOGL',
  'MSFT',
  'AMZN',
  'TSLA',
  'META',
  'NVDA',
  'BTC-USD',
  'ETH-USD',
];

// Fetch stock data from Yahoo Finance (no API key required)
async function fetchStockPrice(symbol) {
  try {
    // Check cache first
    const cached = cache.get(symbol);
    if (cached) {
      return cached;
    }

    // Yahoo Finance query API (public, no key required)
    const url = `https://query1.finance.yahoo.com/v8/finance/chart/${symbol}`;
    const response = await axios.get(url, {
      timeout: 5000,
      headers: {
        'User-Agent': 'Mozilla/5.0',
      },
      httpAgent: httpAgent,
      httpsAgent: httpsAgent,
    });

    const data = response.data;
    if (!data.chart || !data.chart.result || data.chart.result.length === 0) {
      throw new Error('Invalid response from Yahoo Finance');
    }

    const result = data.chart.result[0];
    const quote = result.meta;
    const currentPrice = quote.regularMarketPrice || quote.previousClose;
    const previousClose = quote.previousClose || quote.chartPreviousClose;

    // Calculate change percentage
    const changePercent = previousClose
      ? ((currentPrice - previousClose) / previousClose) * 100
      : 0;

    const stockData = {
      symbol: symbol,
      price: parseFloat(currentPrice.toFixed(2)),
      changePercent: parseFloat(changePercent.toFixed(2)),
      lastUpdated: new Date(),
    };

    // Cache the result
    cache.set(symbol, stockData);

    // Return immediately, database update will be batched
    return stockData;
  } catch (error) {
    console.error(`Error fetching stock ${symbol}:`, error.message);

    // Try to get from database as fallback
    const dbData = await getStockFromDatabase(symbol);
    if (dbData) {
      return dbData;
    }

    // Return mock data if all else fails
    return {
      symbol: symbol,
      price: Math.random() * 1000 + 100,
      changePercent: (Math.random() - 0.5) * 10,
      lastUpdated: new Date(),
    };
  }
}

// Batch buffer for database writes
let batchBuffer = [];
let batchTimeout = null;
const BATCH_SIZE = 50;
const BATCH_DELAY_MS = 1000;

// Update stock price in database (batched)
async function updateStockInDatabase(stockData) {
  batchBuffer.push(stockData);

  // If batch is full, flush immediately
  if (batchBuffer.length >= BATCH_SIZE) {
    clearTimeout(batchTimeout);
    await flushBatchToDatabase();
  } else if (!batchTimeout) {
    // Otherwise, schedule a flush
    batchTimeout = setTimeout(flushBatchToDatabase, BATCH_DELAY_MS);
  }
}

// Flush batch buffer to database
async function flushBatchToDatabase() {
  if (batchBuffer.length === 0) return;

  const batch = [...batchBuffer];
  batchBuffer = [];
  batchTimeout = null;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Use prepared statement for better performance
    const query = `
      INSERT INTO stock_prices (symbol, price, change_percent, last_updated)
      VALUES ($1, $2, $3, $4)
      ON CONFLICT (symbol)
      DO UPDATE SET
        price = EXCLUDED.price,
        change_percent = EXCLUDED.change_percent,
        last_updated = EXCLUDED.last_updated
    `;

    // Batch insert all stocks
    for (const stockData of batch) {
      await client.query(query, [
        stockData.symbol,
        stockData.price,
        stockData.changePercent,
        stockData.lastUpdated,
      ]);
    }

    await client.query('COMMIT');
    console.log(`Flushed ${batch.length} stock updates to database`);
  } catch (error) {
    await client.query('ROLLBACK');
    console.error('Error flushing batch to database:', error);
  } finally {
    client.release();
  }
}

// Get stock from database
async function getStockFromDatabase(symbol) {
  const client = await pool.connect();
  try {
    const result = await client.query(
      'SELECT symbol, price, change_percent, last_updated FROM stock_prices WHERE symbol = $1',
      [symbol],
    );

    if (result.rows.length > 0) {
      const row = result.rows[0];
      return {
        symbol: row.symbol,
        price: parseFloat(row.price),
        changePercent: parseFloat(row.change_percent),
        lastUpdated: row.last_updated,
      };
    }
    return null;
  } finally {
    client.release();
  }
}

// Fetch multiple stocks with batch database update
async function fetchMultipleStocks(symbols = DEFAULT_SYMBOLS) {
  const promises = symbols.map((symbol) => fetchStockPrice(symbol));
  const results = await Promise.allSettled(promises);

  const stocks = results
    .filter((result) => result.status === 'fulfilled')
    .map((result) => result.value);

  // Batch update all stocks to database
  for (const stock of stocks) {
    await updateStockInDatabase(stock);
  }

  return stocks;
}

// Cache for database query results
let dbQueryCache = null;
let dbQueryCacheTime = 0;
const DB_CACHE_TTL = 5000; // 5 seconds

// Get all stocks from database with query result caching
async function getAllStocksFromDatabase() {
  const now = Date.now();

  // Return cached result if still valid
  if (dbQueryCache && (now - dbQueryCacheTime) < DB_CACHE_TTL) {
    return dbQueryCache;
  }

  const client = await pool.connect();
  try {
    const result = await client.query(
      'SELECT symbol, price, change_percent, last_updated FROM stock_prices ORDER BY symbol',
    );

    const stocks = result.rows.map((row) => ({
      symbol: row.symbol,
      price: parseFloat(row.price),
      changePercent: parseFloat(row.change_percent),
      lastUpdated: row.last_updated,
    }));

    // Update cache
    dbQueryCache = stocks;
    dbQueryCacheTime = now;

    return stocks;
  } finally {
    client.release();
  }
}

// Initialize stock data on startup
async function initializeStockData() {
  console.log('Initializing stock data...');
  await fetchMultipleStocks(DEFAULT_SYMBOLS);
  console.log('Stock data initialized');
}

// Graceful shutdown - flush remaining batch
async function shutdown() {
  console.log('Flushing remaining stock updates...');
  await flushBatchToDatabase();
  await pool.end();
}

module.exports = {
  fetchStockPrice,
  fetchMultipleStocks,
  getAllStocksFromDatabase,
  initializeStockData,
  DEFAULT_SYMBOLS,
  shutdown,
  flushBatchToDatabase,
};

// Made with Bob
