/**
 * Rainmaker Intelligence Engine (RIE)
 *
 * Unified forecast intelligence pipeline. Consolidates PIFF, DIGIMON, DVP,
 * KenPom, Corner Scout, and RAG knowledge base into a single engine.
 *
 * Public API:
 *   buildIntelligence()    — full signal collection + composite scoring
 *   refreshIntelligence()  — re-score with updated odds (no Grok re-run)
 *   mapToLegacyComposite() — backward-compat for existing forecast-builder callers
 */

import { IntelligenceResult, MatchupContext, LegacyCompositeResult, RagInsight } from './types';
import { registerSignal, getSignalRegistry } from './signal-registry';
import { collectSignals } from './signal-collector';
import { computeIntelligence } from './composite-engine';
import { queryRag } from './rag-client';

// Signals
import { piffSignal } from './signals/piff-signal';
import { digimonSignal } from './signals/digimon-signal';
import { dvpSignal } from './signals/dvp-signal';
import { kenpomSignal } from './signals/kenpom-signal';
import { cornerScoutSignal } from './signals/corner-scout-signal';
import { ragSignal } from './signals/rag-signal';
import { fangraphsSignal } from './signals/fangraphs-signal';
import { mlbMatchupSignal } from './signals/mlb-matchup-signal';
import { mlbPhaseSignal } from './signals/mlb-phase-signal';

// Strategies
import { NbaStrategy } from './strategies/nba.strategy';
import { NcaabStrategy } from './strategies/ncaab.strategy';
import { NhlStrategy } from './strategies/nhl.strategy';
import { MlbStrategy } from './strategies/mlb.strategy';
import { createSoccerStrategy, isSoccerLeague } from './strategies/soccer.strategy';
import { MmaStrategy } from './strategies/mma.strategy';
import { BaseStrategy } from './strategies/base.strategy';

// ── Initialize: register all signals ───────────────────────────────────

const FORECAST_PAYLOAD_V2 = process.env.FORECAST_PAYLOAD_V2 === 'true';
const RIE_NATIVE_OUTPUT = process.env.RIE_NATIVE_OUTPUT === 'true';

let initialized = false;

const LEGACY_SCORE_KEY_MAP: Record<string, string> = {
  grok: 'grokScore',
  piff: 'piffScore',
  digimon: 'digimonScore',
  dvp: 'dvpScore',
  rag: 'ragScore',
  kenpom: 'kenpomScore',
  fangraphs: 'fangraphsScore',
  corner_scout: 'cornerScoutScore',
  mlb_matchup: 'mlbMatchupScore',
  mlb_phase: 'mlbPhaseScore',
};

function round3(value: number): number {
  return Math.round(value * 1000) / 1000;
}

function clamp(value: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, value));
}

function toLegacyScoreKey(signalId: string): string {
  return LEGACY_SCORE_KEY_MAP[signalId]
    || `${signalId.replace(/_([a-z])/g, (_match, char: string) => char.toUpperCase())}Score`;
}

function toContributionKey(scoreKey: string): string {
  return scoreKey.replace(/Score$/, 'Contribution');
}

function buildLegacyEdgeBreakdown(
  intel: IntelligenceResult,
  extras?: {
    fallbackGrokScore?: number;
    fallbackPiffScore?: number;
    fallbackDigimonScore?: number | null;
    extraAdjustmentContribution?: number;
    kenpomMOV?: number;
  },
): LegacyCompositeResult['edgeBreakdown'] & Record<string, any> {
  const edgeBreakdown: Record<string, any> = {
    grokScore: extras?.fallbackGrokScore ?? 0.5,
    piffScore: extras?.fallbackPiffScore ?? 0.5,
    digimonScore: extras?.fallbackDigimonScore ?? null,
    weights: {},
  };

  let contributionSum = 0;

  for (const [signalId, value] of Object.entries(intel.edgeBreakdown || {})) {
    const scoreKey = toLegacyScoreKey(signalId);
    const contributionKey = toContributionKey(scoreKey);
    const roundedScore = round3(value.score);
    const roundedWeight = round3(value.weight);
    const roundedContribution = round3(value.contribution);

    edgeBreakdown[scoreKey] = roundedScore;
    edgeBreakdown[contributionKey] = roundedContribution;
    edgeBreakdown.weights[signalId] = roundedWeight;
    contributionSum += roundedContribution;
  }

  if (typeof extras?.kenpomMOV === 'number') {
    edgeBreakdown.kenpomMOV = extras.kenpomMOV;
  }

  const adjustmentContribution = round3(
    (extras?.extraAdjustmentContribution ?? 0) + round3(intel.compositeConfidence) - round3(contributionSum),
  );

  if (adjustmentContribution !== 0) {
    edgeBreakdown.adjustmentContribution = adjustmentContribution;
  }

  return edgeBreakdown as LegacyCompositeResult['edgeBreakdown'] & Record<string, any>;
}

export function reconstructLegacyCompositeConfidence(
  source: Pick<LegacyCompositeResult, 'edgeBreakdown'> | LegacyCompositeResult['edgeBreakdown'] | null | undefined,
): number {
  if (!source) return 0.5;

  const edgeBreakdown: Record<string, any> =
    'edgeBreakdown' in source ? (source as Pick<LegacyCompositeResult, 'edgeBreakdown'>).edgeBreakdown : source;

  let total = 0;
  const contributionKeys = Object.keys(edgeBreakdown).filter((key) => key.endsWith('Contribution'));

  if (contributionKeys.length > 0) {
    for (const key of contributionKeys) {
      if (typeof edgeBreakdown[key] === 'number') {
        total += edgeBreakdown[key];
      }
    }
  } else {
    const weights = edgeBreakdown.weights && typeof edgeBreakdown.weights === 'object' ? edgeBreakdown.weights : {};
    for (const [signalId, rawWeight] of Object.entries(weights)) {
      const score = edgeBreakdown[toLegacyScoreKey(signalId)];
      const weight = Number(rawWeight);
      if (typeof score === 'number' && Number.isFinite(weight)) {
        total += score * weight;
      }
    }
    if (typeof edgeBreakdown.adjustmentContribution === 'number') {
      total += edgeBreakdown.adjustmentContribution;
    }
  }

  return round3(clamp(total, 0, 1));
}

export function applyLegacyConfidenceAdjustment(
  legacy: LegacyCompositeResult,
  adjustmentContribution: number,
): LegacyCompositeResult {
  if (!adjustmentContribution) return legacy;

  const edgeBreakdown = legacy.edgeBreakdown as Record<string, any>;
  const existingAdjustment = typeof edgeBreakdown.adjustmentContribution === 'number'
    ? edgeBreakdown.adjustmentContribution
    : 0;

  edgeBreakdown.adjustmentContribution = round3(existingAdjustment + adjustmentContribution);
  legacy.compositeConfidence = reconstructLegacyCompositeConfidence(edgeBreakdown as any);
  return legacy;
}

function ensureInitialized() {
  if (initialized) return;
  registerSignal(piffSignal);
  registerSignal(digimonSignal);
  registerSignal(dvpSignal);
  registerSignal(kenpomSignal);
  registerSignal(cornerScoutSignal);
  registerSignal(ragSignal);
  registerSignal(fangraphsSignal);
  registerSignal(mlbMatchupSignal);
  registerSignal(mlbPhaseSignal);
  initialized = true;
}

// ── Strategy factory ───────────────────────────────────────────────────

function getStrategy(league: string): BaseStrategy {
  const l = league.toLowerCase();
  if (l === 'nba') return new NbaStrategy();
  if (l === 'ncaab') return new NcaabStrategy();
  if (l === 'nhl') return new NhlStrategy();
  if (l === 'mlb') return new MlbStrategy();
  if (l === 'mma') return new MmaStrategy();
  if (isSoccerLeague(l)) return createSoccerStrategy(l);

  // Fallback: use NHL strategy (balanced Grok + PIFF + RAG)
  const fallback = new NhlStrategy();
  fallback.league = l;
  return fallback;
}

// ── Public API ─────────────────────────────────────────────────────────

/**
 * Full intelligence pipeline: collect all signals + RAG + compute composite.
 */
export async function buildIntelligence(params: {
  event: any;
  league: string;
  homeTeam: string;
  awayTeam: string;
  homeShort: string;
  awayShort: string;
  startsAt: string;
  eventId: string;
  grokConfidence: number;
  grokValueRating: number;
}): Promise<IntelligenceResult> {
  ensureInitialized();

  const ctx: MatchupContext = {
    league: params.league,
    homeTeam: params.homeTeam,
    awayTeam: params.awayTeam,
    homeShort: params.homeShort,
    awayShort: params.awayShort,
    startsAt: params.startsAt,
    eventId: params.eventId,
    event: params.event,
  };

  const strategy = getStrategy(params.league);

  // Collect all signals in parallel
  const signals = await collectSignals(ctx);

  // Get RAG insights (already included in signals via rag-signal,
  // but also extract for the composite engine's adjustment phase)
  const ragResult = signals.find(s => s.signalId === 'rag');
  const ragInsights: RagInsight[] = ragResult?.rawData?.insights || [];

  // Compute intelligence
  return computeIntelligence({
    signals,
    strategy,
    ragInsights,
    grokConfidence: params.grokConfidence,
    grokValueRating: params.grokValueRating,
  });
}

/**
 * Refresh intelligence with updated signals (no Grok re-run).
 * Re-collects PIFF, DIGIMON, DVP etc. and re-computes composite.
 */
export async function refreshIntelligence(params: {
  event: any;
  league: string;
  homeTeam: string;
  awayTeam: string;
  homeShort: string;
  awayShort: string;
  startsAt: string;
  eventId: string;
  cachedGrokConfidence: number;
  cachedGrokValueRating: number;
}): Promise<IntelligenceResult> {
  // Same as buildIntelligence but uses cached Grok values
  return buildIntelligence({
    event: params.event,
    league: params.league,
    homeTeam: params.homeTeam,
    awayTeam: params.awayTeam,
    homeShort: params.homeShort,
    awayShort: params.awayShort,
    startsAt: params.startsAt,
    eventId: params.eventId,
    grokConfidence: params.cachedGrokConfidence,
    grokValueRating: params.cachedGrokValueRating,
  });
}

// ── Backward Compatibility ─────────────────────────────────────────────

/**
 * Maps IntelligenceResult back to the LegacyCompositeResult shape
 * that forecast-builder.ts, forecast-runner.ts, and all downstream
 * code (caching, archiving, blog gen, narrative engine) expects.
 */
export function mapToLegacyComposite(intel: IntelligenceResult, grokConfidence: number, grokValueRating: number): LegacyCompositeResult {
  const piffSig = intel.signals.find(s => s.signalId === 'piff');
  const digimonSig = intel.signals.find(s => s.signalId === 'digimon');
  const dvpSig = intel.signals.find(s => s.signalId === 'dvp');
  const kenpomSig = intel.signals.find(s => s.signalId === 'kenpom');

  // Build legacy modelSignals shape
  const piffData = piffSig?.available ? {
    avgEdge: piffSig.rawData?.avgEdge || 0,
    legCount: piffSig.rawData?.legCount || 0,
    topPicks: (piffSig.rawData?.topPicks || []).slice(0, 3).map((p: any) => ({
      name: p.name, stat: p.stat, line: p.line, edge: p.edge,
      direction: p.direction || 'over', tier: p.tier || 'T3_SOLID', szn_hr: p.szn_hr,
    })),
  } : null;

  const digimonData = digimonSig?.available ? {
    available: true,
    lockCount: digimonSig.rawData?.lockCount || 0,
    avgMissRate: digimonSig.rawData?.avgMissRate || 0,
    topPicks: (digimonSig.rawData?.topPicks || []).slice(0, 3).map((p: any) => ({
      player: p.player, prop: p.prop, line: p.line, missRate: p.missRate, verdict: p.verdict,
    })),
  } : null;

  const dvpData = dvpSig?.available ? {
    home: dvpSig.rawData?.home || null,
    away: dvpSig.rawData?.away || null,
  } : null;

  const edgeBreakdown = buildLegacyEdgeBreakdown(intel, {
    fallbackGrokScore: intel.edgeBreakdown['grok']?.score ?? grokConfidence,
    fallbackPiffScore: intel.edgeBreakdown['piff']?.score ?? 0.5,
    fallbackDigimonScore: intel.edgeBreakdown['digimon']?.score ?? null,
    kenpomMOV: kenpomSig?.rawData?.predictedMOV,
  });

  const legacy: LegacyCompositeResult = {
    compositeConfidence: reconstructLegacyCompositeConfidence(edgeBreakdown),
    stormCategory: intel.stormCategory,
    modelSignals: {
      grok: { confidence: grokConfidence, valueRating: grokValueRating },
      piff: piffData,
      digimon: digimonData,
      dvp: dvpData,
    },
    edgeBreakdown,
    compositeVersion: intel.compositeVersion,
  };

  if (FORECAST_PAYLOAD_V2) {
    legacy.rieSignals = intel.signals;
    legacy.ragInsights = intel.ragInsights;
    legacy.strategyId = intel.strategyProfile?.league || undefined;
  }

  return legacy;
}

export function isNativeIntelligenceResult(stored: any): stored is IntelligenceResult {
  return !!stored && Array.isArray(stored.signals) && !!stored.strategyProfile && !!stored.edgeBreakdown;
}

export function getStoredPayloadExtensions(stored: any): {
  rieSignals: any[] | null;
  ragInsights: any[] | null;
  strategyId: string | null;
} {
  if (!stored) {
    return { rieSignals: null, ragInsights: null, strategyId: null };
  }

  if (isNativeIntelligenceResult(stored)) {
    return {
      rieSignals: stored.signals || null,
      ragInsights: stored.ragInsights || null,
      strategyId: stored.strategyProfile?.league || null,
    };
  }

  return {
    rieSignals: stored.rieSignals || null,
    ragInsights: stored.ragInsights || null,
    strategyId: stored.strategyId || null,
  };
}

export function getLegacyCompositeView(
  stored: any,
  fallbackGrokConfidence = 0.5,
  fallbackGrokValueRating = 5,
): LegacyCompositeResult | null {
  if (!stored) return null;
  if (stored.modelSignals && stored.edgeBreakdown) return stored as LegacyCompositeResult;
  if (!isNativeIntelligenceResult(stored)) return null;

  const grokScore = stored.edgeBreakdown?.grok?.score ?? fallbackGrokConfidence;
  return mapToLegacyComposite(stored, grokScore, fallbackGrokValueRating);
}

export function formatStoredModelSignals(
  intel: IntelligenceResult | null | undefined,
  legacy: LegacyCompositeResult,
): IntelligenceResult | LegacyCompositeResult {
  if (RIE_NATIVE_OUTPUT && intel) {
    return intel;
  }
  return legacy;
}

// Re-export types for convenience
export type { IntelligenceResult, LegacyCompositeResult, MatchupContext } from './types';
