import { getUniverseAsOf } from './universe.js';
import { getFundamentalsAsOf } from './fundamentals.js';
import { standardizeCrossSection, FACTORS } from './factors.js';
import { spearman, calculateTurnover } from './metrics.js';

// Global reference for forward returns in our testing environment
// In production, this would query a price DB
let globalReturnsMap = new Map();

export function setForwardReturnsMap(map) {
  globalReturnsMap = map;
}

function getForwardReturn(ticker, dateStr, horizonMonths, allDates) {
  // Find the index of the current date
  const idx = allDates.indexOf(dateStr);
  if (idx === -1 || idx + horizonMonths >= allDates.length) return null; // Not enough forward data

  let cumulativeRet = 0;
  // Simple sum for log returns, or compound. We'll use simple compound: (1+r1)*(1+r2) - 1
  let mult = 1.0;
  for (let m = 0; m < horizonMonths; m++) {
    const nextDate = allDates[idx + m];
    const r = globalReturnsMap.get(`${ticker}_${nextDate}`);
    if (r === undefined || r === null) return null; // missing data
    mult *= (1 + r);
  }
  return mult - 1.0;
}

function bucketIntoDeciles(rankedScores) {
  const deciles = Array(10).fill(null).map(() => []);
  const n = rankedScores.length;
  if (n === 0) return deciles;

  for (let i = 0; i < n; i++) {
    const d = Math.min(9, Math.floor((i / n) * 10));
    deciles[d].push(rankedScores[i].ticker);
  }
  return deciles; // deciles[0] = highest scores (Top Decile), deciles[9] = lowest scores
}

export function runFactor(factorName, allDates, horizonMonths = 1, costPerSideBps = 5) {
  const results = [];
  let prevD10Weights = null;
  let prevD1Weights = null;
  const costPct = costPerSideBps / 10000.0;

  for (const date of allDates) {
    const members = getUniverseAsOf(date);
    if (!members || members.length === 0) continue;

    const rawScores = {};
    for (const t of members) {
      const fund = getFundamentalsAsOf(t, date);
      if (!fund) continue;
      
      const score = FACTORS[factorName](fund);
      if (score !== null && !isNaN(score)) {
        rawScores[t] = score;
      }
    }

    // Standardize cross-sectionally
    const zScores = standardizeCrossSection(rawScores, false);
    
    // Sort descending (High Score = Best)
    const ranked = Object.keys(zScores)
      .map(t => ({ ticker: t, score: zScores[t] }))
      .sort((a, b) => b.score - a.score);

    if (ranked.length < 10) continue; // need enough names for deciles

    const deciles = bucketIntoDeciles(ranked);
    const d10Tickers = deciles[0]; // Top decile
    const d1Tickers = deciles[9];  // Bottom decile

    // Calculate equal-weight portfolio weights for this month
    const curD10Weights = {};
    d10Tickers.forEach(t => curD10Weights[t] = 1.0 / d10Tickers.length);
    
    const curD1Weights = {};
    d1Tickers.forEach(t => curD1Weights[t] = 1.0 / d1Tickers.length);

    // Calculate turnover
    const d10Turnover = calculateTurnover(prevD10Weights, curD10Weights);
    const d1Turnover = calculateTurnover(prevD1Weights, curD1Weights);

    // Forward returns
    const decileReturns = [];
    let allFwdReturns = [];
    let allZScores = [];

    for (let d = 0; d < 10; d++) {
      const decTickers = deciles[d];
      let sumRet = 0;
      let count = 0;
      for (const t of decTickers) {
        const ret = getForwardReturn(t, date, horizonMonths, allDates);
        if (ret !== null) {
          sumRet += ret;
          count++;
          
          allFwdReturns.push(ret);
          allZScores.push(zScores[t]);
        }
      }
      decileReturns.push(count > 0 ? sumRet / count : 0);
    }

    if (allFwdReturns.length > 0) {
      const ic = spearman(allZScores, allFwdReturns);
      const universeMeanRet = allFwdReturns.reduce((a, b) => a + b, 0) / allFwdReturns.length;

      // Gross Returns
      const d10Gross = decileReturns[0];
      const d1Gross = decileReturns[9];
      
      // Net Returns
      const d10Net = d10Gross - (d10Turnover * costPct);
      const d1Net = d1Gross - (d1Turnover * costPct);

      results.push({
        date,
        ic,
        decileReturns, // Gross decile returns for the staircase plot
        d10Gross,
        d1Gross,
        d10Net,
        d1Net,
        universeMeanRet,
        d10Turnover,
        d1Turnover
      });
    }

    prevD10Weights = curD10Weights;
    prevD1Weights = curD1Weights;
  }

  return results;
}
