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

const mocked = vi.hoisted(() => ({
  callGrokPersona: vi.fn(),
  buildForecastPrompt: vi.fn(() => 'forecast prompt'),
  buildPiffPropPrompt: vi.fn(() => 'piff prompt'),
  buildNewsReactionPrompt: vi.fn(() => 'news prompt'),
  buildRecapPrompt: vi.fn(() => 'recap prompt'),
  buildDebatePrompt: vi.fn(() => 'debate prompt'),
  buildCelebrityPrompt: vi.fn(() => 'celebrity prompt'),
  insertContent: vi.fn(),
  markTrendProcessed: vi.fn(),
  isDuplicateContent: vi.fn(),
  getRecentPersonaMemory: vi.fn(),
}));

vi.mock('../persona-prompts', () => ({
  callGrokPersona: mocked.callGrokPersona,
  buildForecastPrompt: mocked.buildForecastPrompt,
  buildPiffPropPrompt: mocked.buildPiffPropPrompt,
  buildNewsReactionPrompt: mocked.buildNewsReactionPrompt,
  buildRecapPrompt: mocked.buildRecapPrompt,
  buildDebatePrompt: mocked.buildDebatePrompt,
  buildCelebrityPrompt: mocked.buildCelebrityPrompt,
}));

vi.mock('../data-queries', () => ({
  insertContent: mocked.insertContent,
  markTrendProcessed: mocked.markTrendProcessed,
  isDuplicateContent: mocked.isDuplicateContent,
  getRecentPersonaMemory: mocked.getRecentPersonaMemory,
}));

describe('social-engine content generator', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mocked.insertContent.mockResolvedValue('content-1');
    mocked.markTrendProcessed.mockResolvedValue(undefined);
    mocked.isDuplicateContent.mockResolvedValue(false);
    mocked.getRecentPersonaMemory.mockResolvedValue([]);
  });

  it('builds a fallback image prompt for piff props when the llm omits one', async () => {
    mocked.callGrokPersona.mockResolvedValue({
      text: 'Jalen Green rebounds are getting squeezed by this matchup.',
    });

    const { generateContent } = await import('../content-generator');
    await generateContent([
      {
        persona: {
          id: 1,
          slug: 'the_sharp',
          display_name: 'The Sharp',
          voice_style: 'sharp',
          emoji_prefix: '',
          target_audience: 'bettors',
          content_types: ['piff_prop'],
          weight: 1,
          is_active: true,
        },
        trend: {
          id: 'trend-1',
          trend_type: 'piff_prop',
          signal_source: 'piff',
          sport: 'basketball',
          league: 'nba',
          title: 'Jalen Green UNDER 5.5 rebounds',
          heat_score: 100,
          expires_at: new Date('2026-04-09T00:00:00Z'),
          data: {
            name: 'Jalen Green',
            team: 'Houston',
            opponent: 'Phoenix',
            direction: 'under',
            line: 5.5,
            stat: 'rebounds',
          },
        },
      },
    ]);

    expect(mocked.insertContent).toHaveBeenCalledTimes(1);
    expect(mocked.insertContent.mock.calls[0]?.[0]).toEqual(
      expect.objectContaining({
        image_prompt: expect.stringContaining('Jalen Green'),
      }),
    );
    expect(String(mocked.insertContent.mock.calls[0]?.[0].image_prompt)).toContain('rebounds');
  });

  it('keeps the llm image prompt when one is returned', async () => {
    mocked.callGrokPersona.mockResolvedValue({
      text: 'Projection likes the dog here.',
      imagePrompt: 'custom prompt from llm',
    });

    const { generateContent } = await import('../content-generator');
    await generateContent([
      {
        persona: {
          id: 1,
          slug: 'the_sharp',
          display_name: 'The Sharp',
          voice_style: 'sharp',
          emoji_prefix: '',
          target_audience: 'bettors',
          content_types: ['forecast_pick'],
          weight: 1,
          is_active: true,
        },
        trend: {
          id: 'trend-2',
          trend_type: 'forecast_pick',
          signal_source: 'forecast',
          sport: 'basketball',
          league: 'nba',
          title: 'Mavs at Suns',
          heat_score: 80,
          expires_at: new Date('2026-04-09T00:00:00Z'),
          data: {
            away_team: 'Dallas',
            home_team: 'Phoenix',
          },
        },
      },
    ]);

    expect(mocked.insertContent.mock.calls[0]?.[0]).toEqual(
      expect.objectContaining({
        image_prompt: 'custom prompt from llm',
      }),
    );
  });

  it('caps recent memory snippets before sending the prompt to the llm', async () => {
    mocked.buildNewsReactionPrompt.mockReturnValue('news prompt');
    mocked.getRecentPersonaMemory.mockResolvedValue([
      'A'.repeat(220),
      'B'.repeat(220),
      'C'.repeat(220),
      'D'.repeat(220),
      'E'.repeat(220),
    ]);
    mocked.callGrokPersona.mockResolvedValue({
      text: 'Fresh angle without repeating the same opener.',
    });

    const { generateContent } = await import('../content-generator');
    await generateContent([
      {
        persona: {
          id: 4,
          slug: 'the_culture_host',
          display_name: 'The Culture Host',
          voice_style: 'host',
          emoji_prefix: '',
          target_audience: 'fans',
          content_types: ['curated_news'],
          weight: 1,
          is_active: true,
        },
        trend: {
          id: 'trend-3',
          trend_type: 'curated_news',
          signal_source: 'news',
          sport: 'basketball',
          league: 'nba',
          title: 'Late game chaos',
          heat_score: 75,
          expires_at: new Date('2026-04-09T00:00:00Z'),
          data: {
            source: 'ESPN',
            summary: 'Late game chaos changes the playoff picture.',
          },
        },
      },
    ]);

    const prompt = String(mocked.callGrokPersona.mock.calls[0]?.[0] || '');
    expect(prompt).toContain('RECENT POSTS TO AVOID ECHOING');
    expect(prompt).toContain('1. ');
    expect(prompt).toContain('4. ');
    expect(prompt).not.toContain('5. ');
    expect(prompt).toContain('…');
  });
});
