import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { UNIVERSE } from '../services/stockUniverseService.js';
import { LANES } from './config.js';
import { simulateSwing } from './outcomeSim.js';
import { swingFeatures } from './features.js';
import { StandardScaler } from './model/scaler.js';
import { LogisticModel } from './model/logistic.js';
import { fitGradeThresholds, toGrade } from './grades.js';
import { generateResultsTable } from './report/resultsTable.js';
import { calibration } from './report/calibration.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CACHE_DIR = path.join(__dirname, 'cache');

const BACKTEST_RANGE = '2y';
const YF_BASE = 'https://query1.finance.yahoo.com';
const BURN_IN_DAYS = 200; // Need 200 days for 200-SMA

// Fetch historical data with caching
async function fetchHistoricalData(symbol) {
  const cachePath = path.join(CACHE_DIR, `${symbol}_${BACKTEST_RANGE}.json`);
  if (fs.existsSync(cachePath)) {
    return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
  }
  console.log(`[Fetch] Downloading ${symbol} data...`);
  const url = `${YF_BASE}/v8/finance/chart/${symbol}?interval=1d&range=${BACKTEST_RANGE}`;
  const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
  if (!res.ok) return null;
  const data = await res.json();
  const result = data?.chart?.result?.[0];
  if (result) fs.writeFileSync(cachePath, JSON.stringify(result));
  return result;
}

function extractCandles(yfResult) {
  const quotes = yfResult.indicators?.quote?.[0] || {};
  const timestamps = yfResult.timestamp || [];
  const candles = [];
  for (let i = 0; i < timestamps.length; i++) {
    if (quotes.high?.[i] != null && quotes.low?.[i] != null && quotes.close?.[i] != null && quotes.open?.[i] != null) {
      candles.push({
        ts: timestamps[i] * 1000,
        date: new Date(timestamps[i] * 1000).toISOString().split('T')[0],
        open: quotes.open[i],
        high: quotes.high[i],
        low: quotes.low[i],
        close: quotes.close[i],
        volume: quotes.volume?.[i] || 0
      });
    }
  }
  return candles;
}

async function runValidationGate() {
  console.log(`\n======================================================`);
  console.log(`   PATH C (SWING) VALIDATION GATE`);
  console.log(`======================================================\n`);
  
  const cfg = LANES.swing;
  const dataMap = new Map();
  const symbols = ['SPY', '^VIX', ...UNIVERSE.map(s => s.symbol)];
  
  // 0. Load Data
  process.stdout.write("Loading historical data... ");
  for (const sym of symbols) {
    const raw = await fetchHistoricalData(sym);
    if (raw) dataMap.set(sym, extractCandles(raw));
  }
  console.log("Done.");

  const spyCandles = dataMap.get('SPY');
  const vixCandles = dataMap.get('^VIX');
  
  const allSignals = [];
  
  // 1. Generate ALL signals over full window
  process.stdout.write("Generating signals (simulating multi-day paths)... ");
  
  for (let i = BURN_IN_DAYS; i < spyCandles.length - 1; i++) {
    const currentSpyDate = spyCandles[i].date;
    const currentVixDate = spyCandles[i].date; // approx
    
    const spySlice = spyCandles.slice(0, i + 1);
    
    // get VIX close
    const vixSlice = vixCandles?.filter(c => c.date <= currentSpyDate);
    const vixClose = vixSlice?.length ? vixSlice[vixSlice.length - 1].close : 15;

    for (const u of UNIVERSE) {
      const candles = dataMap.get(u.symbol);
      if (!candles) continue;
      
      const stockIdx = candles.findIndex(c => c.date === currentSpyDate);
      if (stockIdx === -1) continue;

      const stockSlice = candles.slice(0, stockIdx + 1);
      
      // Compute point-in-time features (no lookahead!)
      const features = swingFeatures(stockSlice, spySlice, vixClose);
      if (!features) continue;

      // Simulate outcome (multi-day) using the real entry index which is stockIdx + 1 (tomorrow's open)
      const entryIdx = stockIdx + 1; 
      const result = simulateSwing(candles, entryIdx, cfg);
      
      if (result) {
        allSignals.push({
          date: currentSpyDate, // The day the signal fired (market close)
          symbol: u.symbol,
          features,
          outcome: result.outcome,
          rMultiple: result.r
        });
      }
    }
  }
  console.log(`Done. (${allSignals.length} signals generated)`);

  if (allSignals.length === 0) {
    console.error("No signals generated!");
    return;
  }

  // 2. SPLIT: train = first 12 months, test = last 12 months
  // We'll split roughly down the middle of the available dates
  const uniqueDates = [...new Set(allSignals.map(s => s.date))].sort();
  const splitDate = uniqueDates[Math.floor(uniqueDates.length / 2)];
  
  const trainSet = allSignals.filter(s => s.date < splitDate);
  const testSet = allSignals.filter(s => s.date >= splitDate);
  
  console.log(`Split Date: ${splitDate}`);
  console.log(`TRAIN Set: ${trainSet.length} signals`);
  console.log(`TEST Set:  ${testSet.length} signals`);

  // 3. scaler.fit(train.features)
  const scaler = new StandardScaler();
  scaler.fit(trainSet.map(s => s.features));
  
  // 4. model = logistic.fit(scaler.transform(train.features), train.win)
  const model = new LogisticModel();
  const trainFeaturesScaled = scaler.transformArray(trainSet.map(s => s.features));
  const trainLabels = trainSet.map(s => s.outcome === 'WIN' ? 1 : 0);
  
  const coeffs = model.fit(trainFeaturesScaled, trainLabels);
  
  console.log(`\n=== MODEL FIT (Standardized Coefficients) ===`);
  console.log(`Intercept:      ${coeffs.intercept.toFixed(4)}`);
  for (const k of Object.keys(coeffs)) {
    if (k !== 'intercept') console.log(`${k.padEnd(15)} ${coeffs[k].toFixed(4)}`);
  }

  // 5. thresholds = fitGradeThresholds(model.predict(train))
  const trainProbs = model.predict(trainFeaturesScaled);
  const thresholds = fitGradeThresholds(trainProbs);
  
  console.log(`\n=== GRADE THRESHOLDS (FROZEN ON TRAIN) ===`);
  console.log(`Grade A min P(win): ${(thresholds.aMin*100).toFixed(1)}%`);
  console.log(`Grade B min P(win): ${(thresholds.bMin*100).toFixed(1)}%`);
  console.log(`Grade C min P(win): ${(thresholds.cMin*100).toFixed(1)}%`);

  // ===================== NO TRAIN DATA PAST THIS POINT =====================

  // 6. test.prob = model.predict(scaler.transform(test.features))
  const testFeaturesScaled = scaler.transformArray(testSet.map(s => s.features));
  const testProbs = model.predict(testFeaturesScaled);

  for (let i = 0; i < testSet.length; i++) {
    testSet[i].prob = testProbs[i];
    testSet[i].grade = toGrade(testProbs[i], thresholds);
  }

  // 7. resultsTable(test)
  generateResultsTable(testSet);

  // 8. calibration(test)
  calibration(testSet);
  
  console.log(`\n======================================================`);
  console.log(`   VALIDATION GATE COMPLETE`);
  console.log(`======================================================\n`);
}

runValidationGate().catch(console.error);
