#!/usr/bin/env node

// Finnhub stock market CLI tool
// Usage: stonks quote <symbol>          — current price
//        stonks search <query>          — search for symbols
//        stonks candles <symbol> [days] — price history (default: 30 days)
//        stonks news <symbol> [days]    — company news (default: 7 days)
//        stonks profile <symbol>        — company profile
//        stonks portfolio               — show virtual portfolio
//        stonks buy <symbol> <amount>   — virtual buy (amount in EUR)
//        stonks sell <symbol> [amount]  — virtual sell (amount in EUR, default: all)
//        stonks limit-buy <symbol> <price> <amount> — set limit buy
//        stonks limit-sell <symbol> <price> [amount] — set limit sell
//        stonks check-limits            — check & execute triggered limit orders
//        stonks history                 — order history

const https = require('https');
const fs = require('fs');
const path = require('path');

// Load API key from global services.env
const envPath = path.join(__dirname, '..', '.credentials', 'services.env');
let API_KEY = '';
try {
  const env = fs.readFileSync(envPath, 'utf8');
  const match = env.match(/FINNHUB_API_KEY=(.+)/);
  if (match) API_KEY = match[1].trim();
} catch (e) {}

if (!API_KEY || API_KEY === 'FILL_IN') {
  console.error('ERROR: Set FINNHUB_API_KEY in .credentials/finnhub.env');
  process.exit(1);
}

const PORTFOLIO_PATH = path.join(__dirname, '..', 'memory', 'portfolio.json');

function loadPortfolio() {
  try {
    return JSON.parse(fs.readFileSync(PORTFOLIO_PATH, 'utf8'));
  } catch (e) {
    const initial = {
      startingCapital: 1000,
      cash: 1000,
      currency: 'EUR',
      positions: [],
      limitOrders: [],
      history: [],
      created: new Date().toISOString()
    };
    fs.writeFileSync(PORTFOLIO_PATH, JSON.stringify(initial, null, 2));
    return initial;
  }
}

function savePortfolio(p) {
  fs.writeFileSync(PORTFOLIO_PATH, JSON.stringify(p, null, 2));
}

function httpGet(url) {
  const mod = url.startsWith('https') ? https : require('http');
  return new Promise((resolve, reject) => {
    mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
      let data = '';
      res.on('data', (c) => data += c);
      res.on('end', () => {
        try { resolve(JSON.parse(data)); }
        catch (e) { reject(new Error(`Parse error: ${data.slice(0, 200)}`)); }
      });
    }).on('error', reject);
  });
}

function finnhub(endpoint) {
  return httpGet(`https://finnhub.io/api/v1${endpoint}&token=${API_KEY}`);
}

// Onvista fallback for EU ETFs/stocks not covered by Finnhub
async function onvistaSearch(query) {
  const r = await httpGet(`https://api.onvista.de/api/v1/instruments/search?searchValue=${encodeURIComponent(query)}`);
  return r && r.list ? r.list : [];
}

async function onvistaQuote(entityValue) {
  const r = await httpGet(`https://api.onvista.de/api/v1/instruments/FUND/${entityValue}/snapshot`);
  if (r && r.quote) return r.quote;
  // Try STOCK type
  const r2 = await httpGet(`https://api.onvista.de/api/v1/instruments/STOCK/${entityValue}/snapshot`);
  return r2 && r2.quote ? r2.quote : null;
}

// ISIN-to-onvista entity cache
const ONVISTA_CACHE_PATH = path.join(__dirname, '..', 'memory', 'onvista-cache.json');
function loadOnvistaCache() {
  try { return JSON.parse(fs.readFileSync(ONVISTA_CACHE_PATH, 'utf8')); } catch { return {}; }
}
function saveOnvistaCache(c) {
  fs.writeFileSync(ONVISTA_CACHE_PATH, JSON.stringify(c, null, 2));
}

async function onvistaQuoteByISIN(isin, name) {
  const cache = loadOnvistaCache();
  let entityValue = cache[isin];
  if (!entityValue) {
    const results = await onvistaSearch(name || isin);
    const match = results.find(r => r.isin === isin);
    if (!match) return null;
    entityValue = match.entityValue;
    cache[isin] = entityValue;
    saveOnvistaCache(cache);
  }
  return onvistaQuote(entityValue);
}

async function quote(symbol) {
  // First check if we have this in portfolio with ISIN — prefer onvista for EU instruments
  const p = loadPortfolio();
  const pos = p.positions.find(pos => pos.symbol === symbol || pos.symbol + '.DE' === symbol);
  if (pos && pos.isin) {
    try {
      const oq = await onvistaQuoteByISIN(pos.isin, pos.name);
      if (oq && oq.last) {
        const change = oq.changeAbsolute || 0;
        const changePct = oq.changePrevDay || 0;
        console.log(`${symbol}\t${oq.last}\t${change >= 0 ? '+' : ''}${change.toFixed(2)}\t${changePct >= 0 ? '+' : ''}${changePct.toFixed(2)}%\tH:${oq.high || 'N/A'}\tL:${oq.low || 'N/A'}\tO:${oq.open || 'N/A'}\tPC:${oq.previousLast || 'N/A'}\t(onvista)`);
        return { c: oq.last, d: change, dp: changePct, h: oq.high, l: oq.low, o: oq.open, pc: oq.previousLast };
      }
    } catch (e) {}
  }
  // Try Finnhub
  const q = await finnhub(`/quote?symbol=${encodeURIComponent(symbol)}`);
  if (q && q.c && q.c !== 0) {
    const change = q.d || 0;
    const changePct = q.dp || 0;
    console.log(`${symbol}\t${q.c}\t${change >= 0 ? '+' : ''}${change}\t${changePct >= 0 ? '+' : ''}${changePct}%\tH:${q.h}\tL:${q.l}\tO:${q.o}\tPC:${q.pc}`);
    return q;
  }
  // Last resort: check portfolio for ISIN without match above
  if (pos && pos.isin) {
    try {
      const oq = await onvistaQuoteByISIN(pos.isin, pos.name);
      if (oq && oq.last) {
        const change = oq.changeAbsolute || 0;
        const changePct = oq.changePrevDay || 0;
        console.log(`${symbol}\t${oq.last}\t${change >= 0 ? '+' : ''}${change.toFixed(2)}\t${changePct >= 0 ? '+' : ''}${changePct.toFixed(2)}%\tH:${oq.high || 'N/A'}\tL:${oq.low || 'N/A'}\tO:${oq.open || 'N/A'}\tPC:${oq.previousLast || 'N/A'}\t(onvista)`);
        return { c: oq.last, d: change, dp: changePct, h: oq.high, l: oq.low, o: oq.open, pc: oq.previousLast };
      }
    } catch (e) {}
  }
  console.error(`No data for ${symbol}. Try search to find the right symbol.`);
  return null;
}

async function search(query) {
  const r = await finnhub(`/search?q=${encodeURIComponent(query)}`);
  if (!r || !r.result || r.result.length === 0) {
    console.log('No results found.');
    return;
  }
  // Show top 15 results
  r.result.slice(0, 15).forEach(s => {
    console.log(`${s.symbol}\t${s.description}\t${s.type}`);
  });
}

async function candles(symbol, days = 30) {
  const to = Math.floor(Date.now() / 1000);
  const from = to - (days * 86400);
  const r = await finnhub(`/stock/candle?symbol=${encodeURIComponent(symbol)}&resolution=D&from=${from}&to=${to}`);
  if (!r || r.s === 'no_data') {
    console.log(`No candle data for ${symbol}.`);
    return;
  }
  console.log('Date\tOpen\tHigh\tLow\tClose\tVolume');
  for (let i = 0; i < r.t.length; i++) {
    const d = new Date(r.t[i] * 1000).toISOString().split('T')[0];
    console.log(`${d}\t${r.o[i]}\t${r.h[i]}\t${r.l[i]}\t${r.c[i]}\t${r.v[i]}`);
  }
}

async function news(symbol, days = 7) {
  const to = new Date().toISOString().split('T')[0];
  const from = new Date(Date.now() - days * 86400000).toISOString().split('T')[0];
  const r = await finnhub(`/company-news?symbol=${encodeURIComponent(symbol)}&from=${from}&to=${to}`);
  if (!r || r.length === 0) {
    console.log('No news found.');
    return;
  }
  r.slice(0, 10).forEach(n => {
    const d = new Date(n.datetime * 1000).toISOString().split('T')[0];
    console.log(`${d}\t${n.headline}\t${n.url}`);
  });
}

async function profile(symbol) {
  const r = await finnhub(`/stock/profile2?symbol=${encodeURIComponent(symbol)}`);
  if (!r || !r.name) {
    console.log(`No profile for ${symbol}.`);
    return;
  }
  console.log(`Name:\t${r.name}`);
  console.log(`Country:\t${r.country}`);
  console.log(`Exchange:\t${r.exchange}`);
  console.log(`Industry:\t${r.finnhubIndustry}`);
  console.log(`Market Cap:\t${r.marketCapitalization}M`);
  console.log(`IPO:\t${r.ipo}`);
  console.log(`URL:\t${r.weburl}`);
}

async function showPortfolio() {
  const p = loadPortfolio();
  let totalValue = p.cash;
  
  console.log(`=== Hoid's Virtual Portfolio ===`);
  console.log(`Starting Capital:\t€${p.startingCapital.toFixed(2)}`);
  console.log(`Cash:\t€${p.cash.toFixed(2)}`);
  console.log('');
  
  if (p.positions.length > 0) {
    console.log('Symbol\tShares\tAvg Price\tCurrent\tValue\tP&L\tP&L%');
    for (const pos of p.positions) {
      try {
        let currentPrice = null;
        // Try onvista first for positions with ISIN (EU instruments)
        if (pos.isin) {
          try {
            const oq = await onvistaQuoteByISIN(pos.isin, pos.name);
            if (oq && oq.last) currentPrice = oq.last;
          } catch (e) {}
        }
        // Fallback to Finnhub
        if (!currentPrice) {
          const q = await finnhub(`/quote?symbol=${encodeURIComponent(pos.symbol)}`);
          if (q && q.c && q.c > 0) currentPrice = q.c;
        }
        currentPrice = currentPrice || pos.avgPrice;
        const value = pos.shares * currentPrice;
        const cost = pos.shares * pos.avgPrice;
        const pnl = value - cost;
        const pnlPct = ((pnl / cost) * 100);
        totalValue += value;
        console.log(`${pos.symbol}\t${pos.shares.toFixed(4)}\t${pos.avgPrice.toFixed(2)}\t${currentPrice.toFixed(2)}\t€${value.toFixed(2)}\t${pnl >= 0 ? '+' : ''}€${pnl.toFixed(2)}\t${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%`);
      } catch (e) {
        const value = pos.shares * pos.avgPrice;
        totalValue += value;
        console.log(`${pos.symbol}\t${pos.shares.toFixed(4)}\t${pos.avgPrice.toFixed(2)}\t???\t€${value.toFixed(2)}\t???\t???`);
      }
    }
  } else {
    console.log('No positions.');
  }
  
  console.log('');
  const totalPnl = totalValue - p.startingCapital;
  const totalPnlPct = ((totalPnl / p.startingCapital) * 100);
  console.log(`Total Value:\t€${totalValue.toFixed(2)}`);
  console.log(`Total P&L:\t${totalPnl >= 0 ? '+' : ''}€${totalPnl.toFixed(2)} (${totalPnlPct >= 0 ? '+' : ''}${totalPnlPct.toFixed(1)}%)`);
  
  if (p.limitOrders.length > 0) {
    console.log('');
    console.log('=== Pending Limit Orders ===');
    console.log('ID\tType\tSymbol\tLimit Price\tAmount\tCreated');
    p.limitOrders.forEach(o => {
      console.log(`${o.id}\t${o.type}\t${o.symbol}\t${o.limitPrice}\t€${o.amount.toFixed(2)}\t${o.created.split('T')[0]}`);
    });
  }
}

async function buy(symbol, amount) {
  const p = loadPortfolio();
  amount = parseFloat(amount);
  
  if (amount > p.cash) {
    console.error(`Not enough cash. Have €${p.cash.toFixed(2)}, need €${amount.toFixed(2)}`);
    return;
  }
  
  const q = await finnhub(`/quote?symbol=${encodeURIComponent(symbol)}`);
  if (!q || q.c === 0) {
    console.error(`Cannot get price for ${symbol}`);
    return;
  }
  
  const price = q.c;
  const shares = amount / price;
  
  // Update or create position
  const existing = p.positions.find(pos => pos.symbol === symbol);
  if (existing) {
    const totalCost = (existing.shares * existing.avgPrice) + amount;
    existing.shares += shares;
    existing.avgPrice = totalCost / existing.shares;
  } else {
    p.positions.push({ symbol, shares, avgPrice: price });
  }
  
  p.cash -= amount;
  p.history.push({
    type: 'BUY',
    symbol,
    shares: shares,
    price,
    amount,
    date: new Date().toISOString()
  });
  
  savePortfolio(p);
  console.log(`BUY\t${symbol}\t${shares.toFixed(4)} shares @ ${price}\t€${amount.toFixed(2)}`);
  console.log(`Cash remaining:\t€${p.cash.toFixed(2)}`);
}

async function sell(symbol, amount) {
  const p = loadPortfolio();
  const pos = p.positions.find(pos => pos.symbol === symbol);
  
  if (!pos) {
    console.error(`No position in ${symbol}`);
    return;
  }
  
  const q = await finnhub(`/quote?symbol=${encodeURIComponent(symbol)}`);
  if (!q || q.c === 0) {
    console.error(`Cannot get price for ${symbol}`);
    return;
  }
  
  const price = q.c;
  let sharesToSell;
  
  if (!amount || amount === 'all') {
    sharesToSell = pos.shares;
  } else {
    sharesToSell = parseFloat(amount) / price;
    if (sharesToSell > pos.shares) sharesToSell = pos.shares;
  }
  
  const proceeds = sharesToSell * price;
  const cost = sharesToSell * pos.avgPrice;
  const pnl = proceeds - cost;
  
  pos.shares -= sharesToSell;
  if (pos.shares < 0.0001) {
    p.positions = p.positions.filter(pp => pp.symbol !== symbol);
  }
  
  p.cash += proceeds;
  p.history.push({
    type: 'SELL',
    symbol,
    shares: sharesToSell,
    price,
    amount: proceeds,
    pnl,
    date: new Date().toISOString()
  });
  
  savePortfolio(p);
  console.log(`SELL\t${symbol}\t${sharesToSell.toFixed(4)} shares @ ${price}\t€${proceeds.toFixed(2)}\tP&L: ${pnl >= 0 ? '+' : ''}€${pnl.toFixed(2)}`);
  console.log(`Cash:\t€${p.cash.toFixed(2)}`);
}

function limitOrder(type, symbol, limitPrice, amount) {
  const p = loadPortfolio();
  limitPrice = parseFloat(limitPrice);
  amount = amount ? parseFloat(amount) : null;
  
  if (type === 'limit-buy' && amount > p.cash) {
    console.error(`Not enough cash. Have €${p.cash.toFixed(2)}`);
    return;
  }
  
  const id = Math.random().toString(36).slice(2, 8);
  p.limitOrders.push({
    id,
    type: type === 'limit-buy' ? 'BUY' : 'SELL',
    symbol,
    limitPrice,
    amount: amount || 0,
    created: new Date().toISOString()
  });
  
  savePortfolio(p);
  console.log(`Limit ${type === 'limit-buy' ? 'BUY' : 'SELL'}\t${symbol}\t@ ${limitPrice}\t€${(amount || 0).toFixed(2)}\tID: ${id}`);
}

async function checkLimits() {
  const p = loadPortfolio();
  if (p.limitOrders.length === 0) {
    console.log('No pending limit orders.');
    return;
  }
  
  const triggered = [];
  const remaining = [];
  
  for (const order of p.limitOrders) {
    try {
      const q = await finnhub(`/quote?symbol=${encodeURIComponent(order.symbol)}`);
      const price = q.c;
      
      if (order.type === 'BUY' && price <= order.limitPrice) {
        triggered.push({ ...order, currentPrice: price });
      } else if (order.type === 'SELL' && price >= order.limitPrice) {
        triggered.push({ ...order, currentPrice: price });
      } else {
        remaining.push(order);
        console.log(`PENDING\t${order.type}\t${order.symbol}\tLimit: ${order.limitPrice}\tCurrent: ${price}`);
      }
    } catch (e) {
      remaining.push(order);
    }
  }
  
  p.limitOrders = remaining;
  savePortfolio(p);
  
  for (const order of triggered) {
    console.log(`TRIGGERED\t${order.type}\t${order.symbol}\t@ ${order.currentPrice} (limit: ${order.limitPrice})`);
    if (order.type === 'BUY') {
      await buy(order.symbol, order.amount);
    } else {
      await sell(order.symbol, order.amount || 'all');
    }
  }
}

function showHistory() {
  const p = loadPortfolio();
  if (p.history.length === 0) {
    console.log('No trades yet.');
    return;
  }
  console.log('Date\tType\tSymbol\tShares\tPrice\tAmount\tP&L');
  p.history.forEach(h => {
    const pnl = h.pnl ? `${h.pnl >= 0 ? '+' : ''}€${h.pnl.toFixed(2)}` : '';
    console.log(`${h.date.split('T')[0]}\t${h.type}\t${h.symbol}\t${h.shares.toFixed(4)}\t${h.price}\t€${h.amount.toFixed(2)}\t${pnl}`);
  });
}

// Main
const [,, cmd, ...args] = process.argv;

(async () => {
  try {
    switch (cmd) {
      case 'quote': await quote(args[0]); break;
      case 'search': await search(args.join(' ')); break;
      case 'candles': await candles(args[0], args[1] || 30); break;
      case 'news': await news(args[0], args[1] || 7); break;
      case 'profile': await profile(args[0]); break;
      case 'portfolio': await showPortfolio(); break;
      case 'buy': await buy(args[0], args[1]); break;
      case 'sell': await sell(args[0], args[1]); break;
      case 'limit-buy': limitOrder('limit-buy', args[0], args[1], args[2]); break;
      case 'limit-sell': limitOrder('limit-sell', args[0], args[1], args[2]); break;
      case 'check-limits': await checkLimits(); break;
      case 'history': showHistory(); break;
      default:
        console.log('Usage:');
        console.log('  stonks quote <symbol>              Current price');
        console.log('  stonks search <query>              Search symbols');
        console.log('  stonks candles <symbol> [days]     Price history');
        console.log('  stonks news <symbol> [days]        Company news');
        console.log('  stonks profile <symbol>            Company info');
        console.log('  stonks portfolio                   Show portfolio');
        console.log('  stonks buy <symbol> <amount>       Buy (EUR amount)');
        console.log('  stonks sell <symbol> [amount]      Sell (EUR amount or all)');
        console.log('  stonks limit-buy <sym> <price> <amt>  Limit buy');
        console.log('  stonks limit-sell <sym> <price> [amt] Limit sell');
        console.log('  stonks check-limits                Check triggered limits');
        console.log('  stonks history                     Trade history');
    }
  } catch (e) {
    console.error(`Error: ${e.message}`);
    process.exit(1);
  }
})();
