import { beforeEach, describe, expect, it, vi } from 'vitest';

const parseOdds = vi.fn();
const updateCachedOdds = vi.fn();
const getPiffPropsForGame = vi.fn(() => []);
const loadPiffPropsForDate = vi.fn(() => ({}));
const getDigimonForGame = vi.fn(() => []);
const loadDigimonPicksForDate = vi.fn(() => ({}));
const getDvpForMatchup = vi.fn(async () => ({ home: null, away: null }));
const calculateComposite = vi.fn();
const refreshIntelligence = vi.fn();
const mapToLegacyComposite = vi.fn();
const poolQuery = vi.fn();
const buildForecast = vi.fn();

vi.mock('../sgo', () => ({
  parseOdds,
  makeEventId: vi.fn(),
  deriveEventLifecycleStatus: vi.fn((event: any) => (event?.status?.started ? 'started' : 'scheduled')),
  SgoEvent: {},
}));
vi.mock('../../models/forecast', () => ({ getCachedForecast: vi.fn(), updateCachedOdds, resolveCanonicalEventId: vi.fn(), RmForecast: {} }));
vi.mock('../forecast-builder', () => ({ buildForecast }));
vi.mock('../archive', () => ({ archiveForecast: vi.fn(), linkBlogPost: vi.fn() }));
vi.mock('../sanitizer', () => ({ sanitizeForecastData: vi.fn() }));
vi.mock('../piff', () => ({ getPiffPropsForGame, loadPiffPropsForDate, loadTodaysPiffProps: vi.fn(() => ({})) }));
vi.mock('../digimon', () => ({ getDigimonForGame, loadDigimonPicksForDate, loadTodaysDigimonPicks: vi.fn(() => ({})) }));
vi.mock('../dvp', () => ({ getDvpForMatchup, generateDvpInsight: vi.fn() }));
vi.mock('../composite-score', () => ({ calculateComposite }));
vi.mock('../blog-generator', () => ({ generateBlogPost: vi.fn(), generateSlug: vi.fn(), saveBlogPost: vi.fn() }));
vi.mock('../compliance/pipeline', () => ({ runCompliancePipeline: vi.fn() }));
vi.mock('../home-field-scout', () => ({ generateHcwInsight: vi.fn() }));
vi.mock('../narrative-engine', () => ({ classifyNarrative: vi.fn() }));
vi.mock('../../db', () => ({ default: { query: poolQuery } }));
vi.mock('../rie', async () => {
  const actual = await vi.importActual<any>('../rie');
  return { ...actual, refreshIntelligence, mapToLegacyComposite };
});

describe('forecast-runner', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.resetModules();
    delete process.env.RIE_ENABLED;
    delete process.env.RIE_NATIVE_OUTPUT;
    parseOdds.mockReturnValue({
      moneyline: { home: -120, away: 110 },
      spread: { home: { line: -4.5, odds: -110 }, away: { line: 4.5, odds: -110 } },
      total: { over: { line: 220.5, odds: -110 }, under: { line: 220.5, odds: -110 } },
    });
    buildForecast.mockResolvedValue({
      forecast: { summary: 'rebuilt' },
      forecastId: 'fc-rebuilt',
      confidenceScore: 0.77,
      rawConfidenceScore: 0.72,
      cached: false,
      odds: parseOdds(),
      composite: {
        compositeConfidence: 0.77,
        stormCategory: 4,
        modelSignals: {},
        edgeBreakdown: {},
        compositeVersion: 'v2',
      },
    });
  });

  it('preserves legacy writes when RIE_NATIVE_OUTPUT is off', async () => {
    process.env.RIE_ENABLED = 'true';
    process.env.RIE_NATIVE_OUTPUT = 'false';
    refreshIntelligence.mockResolvedValue({ signals: [], ragInsights: [], edgeBreakdown: {}, inputQuality: {}, strategyProfile: { league: 'nba' } });
    mapToLegacyComposite.mockReturnValue({
      compositeConfidence: 0.81,
      stormCategory: 5,
      modelSignals: { grok: { confidence: 0.78, valueRating: 8 }, piff: null, digimon: null, dvp: null },
      edgeBreakdown: { grokScore: 0.78, piffScore: 0.5, digimonScore: null, weights: { grok: 1 } },
      compositeVersion: 'rm_2.0',
    });

    const { refreshForEvent } = await import('../forecast-runner');
    await refreshForEvent({
      eventID: 'evt-1',
      teams: { home: { names: { long: 'Lakers', short: 'LAL' } }, away: { names: { long: 'Celtics', short: 'BOS' } } },
      status: { startsAt: '2026-03-27T19:00:00.000Z' },
    } as any, 'nba', {
      id: 'fc-1',
      event_id: 'evt-1',
      odds_data: parseOdds(),
      forecast_data: { summary: 'existing', confidence: 0.78, value_rating: 8 },
      narrative_metadata: { confidenceTier: 'medium' },
      input_quality: { overall: 'A' },
      model_signals: {
        modelSignals: {
          grok: { confidence: 0.74, valueRating: 8 },
          piff: null,
          digimon: null,
          dvp: null,
        },
        edgeBreakdown: { grokScore: 0.74, piffScore: 0.5, digimonScore: null, weights: { grok: 1 } },
      },
      composite_confidence: 0.74,
      confidence_score: 0.74,
    } as any, 'test');

    expect(poolQuery).toHaveBeenCalled();
    const updateArgs = poolQuery.mock.calls.find((call) => String(call[0]).includes('SET composite_confidence = $1'));
    expect(updateArgs).toBeDefined();
    expect(updateArgs![1][1]).toContain('"modelSignals"');
    expect(updateArgs![1][0]).toBe(0.81);
    expect(String(updateArgs![0])).toContain('jsonb_set');
    expect(String(updateArgs![0])).not.toContain('confidence_score = $1');
    expect(updateArgs![1][4]).toBe(0.81);
    expect(refreshIntelligence).toHaveBeenCalledWith(expect.objectContaining({
      cachedGrokConfidence: 0.74,
      cachedGrokValueRating: 8,
    }));
  });

  it('refreshes legacy composites with event-date side-data', async () => {
    calculateComposite.mockReturnValue({
      compositeConfidence: 0.64,
      stormCategory: 3,
      modelSignals: { grok: { confidence: 0.61, valueRating: 6 }, piff: null, digimon: null, dvp: null },
      edgeBreakdown: { grokScore: 0.61, piffScore: 0.5, digimonScore: null, weights: { grok: 0.6, piff: 0.4, digimon: 0 } },
      compositeVersion: 'v2',
    });

    const { refreshForEvent } = await import('../forecast-runner');
    await refreshForEvent({
      eventID: 'evt-soccer-1',
      teams: { home: { names: { long: 'Rayo Vallecano', short: 'RAY' } }, away: { names: { long: 'Elche', short: 'ELC' } } },
      status: { startsAt: '2026-04-03T19:00:00.000Z' },
    } as any, 'la_liga', {
      id: 'fc-soccer-1',
      event_id: 'evt-soccer-1',
      odds_data: parseOdds(),
      forecast_data: { summary: 'existing', confidence: 0.61, value_rating: 6 },
      narrative_metadata: { confidenceTier: 'medium' },
      input_quality: { overall: 'B' },
      model_signals: { modelSignals: { grok: { confidence: 0.61, valueRating: 6 }, piff: null, digimon: null, dvp: null } },
      composite_confidence: 0.61,
      confidence_score: 0.61,
    } as any, 'soccer test');

    expect(loadPiffPropsForDate).toHaveBeenCalledWith('2026-04-03');
  });

  it('rebuilds the full forecast body on material scheduled line movement', async () => {
    parseOdds.mockReturnValue({
      moneyline: { home: -180, away: 150 },
      spread: { home: { line: -1.5, odds: -110 }, away: { line: 1.5, odds: -110 } },
      total: { over: { line: 220.5, odds: -110 }, under: { line: 220.5, odds: -110 } },
    });

    const { refreshForEvent } = await import('../forecast-runner');
    const result = await refreshForEvent({
      eventID: 'evt-rebuild-1',
      teams: { home: { names: { long: 'Lakers', short: 'LAL' } }, away: { names: { long: 'Celtics', short: 'BOS' } } },
      status: { startsAt: '2099-03-27T19:00:00.000Z' },
    } as any, 'nba', {
      id: 'fc-1',
      event_id: 'evt-rebuild-1',
      odds_data: {
        moneyline: { home: -120, away: 110 },
        spread: { home: { line: -4.5, odds: -110 }, away: { line: 4.5, odds: -110 } },
        total: { over: { line: 220.5, odds: -110 }, under: { line: 220.5, odds: -110 } },
      },
      forecast_data: { summary: 'existing summary', confidence: 0.72, value_rating: 7 },
      narrative_metadata: { confidenceTier: 'medium' },
      input_quality: { overall: 'B' },
      model_signals: { modelSignals: { grok: { confidence: 0.72, valueRating: 7 } } },
      composite_confidence: 0.72,
      confidence_score: 0.72,
      composite_version: 'v1',
    } as any, 'rebuild test');

    expect(buildForecast).toHaveBeenCalledWith(expect.objectContaining({
      eventID: 'evt-rebuild-1',
    }), 'nba', { ignoreCache: true });
    expect(updateCachedOdds).not.toHaveBeenCalled();
    expect(result.refreshMode).toBe('full_rebuild');
    expect(result.newConfidence).toBe(0.77);
    expect(result.compositeVersion).toBe('v2');
  });

  it('rebuilds the full forecast body when cached narrative metadata is missing', async () => {
    const { refreshForEvent } = await import('../forecast-runner');
    const result = await refreshForEvent({
      eventID: 'evt-rebuild-missing',
      teams: { home: { names: { long: 'Lakers', short: 'LAL' } }, away: { names: { long: 'Celtics', short: 'BOS' } } },
      status: { startsAt: '2099-03-27T19:00:00.000Z' },
    } as any, 'nba', {
      id: 'fc-2',
      event_id: 'evt-rebuild-missing',
      odds_data: parseOdds(),
      forecast_data: { confidence: 0.72, value_rating: 7 },
      narrative_metadata: null,
      input_quality: null,
      model_signals: { modelSignals: { grok: { confidence: 0.72, valueRating: 7 } } },
      composite_confidence: 0.72,
      confidence_score: 0.72,
      composite_version: 'v1',
    } as any, 'missing meta');

    expect(buildForecast).toHaveBeenCalledWith(expect.objectContaining({
      eventID: 'evt-rebuild-missing',
    }), 'nba', { ignoreCache: true });
    expect(result.refreshMode).toBe('full_rebuild');
  });
});
