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

const mocked = vi.hoisted(() => ({
  poolQuery: vi.fn(),
  poolEnd: vi.fn(),
  poolOn: vi.fn(),
}));

vi.mock('../../db', () => ({
  default: {
    query: mocked.poolQuery,
    end: mocked.poolEnd,
    on: mocked.poolOn,
  },
}));

describe('data-retention worker', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.resetModules();
    process.env.VITEST = 'true';
    delete process.env.PRECOMPUTED_RETENTION_DAYS;
    delete process.env.FORECAST_CACHE_RETENTION_DAYS;
    delete process.env.EVENTS_RETENTION_DAYS;
  });

  it('purges expired precomputed assets with the default retention window', async () => {
    mocked.poolQuery.mockResolvedValueOnce({ rows: [{ count: 11 }] });

    const { purgeExpiredPrecomputedAssets } = await import('../data-retention');
    const removed = await purgeExpiredPrecomputedAssets(new Date('2026-03-30T12:00:00.000Z'));

    expect(removed).toBe(11);
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain("fp.status = 'EXPIRED'");
    expect(mocked.poolQuery.mock.calls[0]?.[1]).toEqual(['2026-03-30T12:00:00.000Z', '7']);
  });

  it('purges stale forecast cache rows only when no accuracy record exists', async () => {
    mocked.poolQuery.mockResolvedValueOnce({ rows: [{ count: 22 }] });

    const { purgeOldForecastCache } = await import('../data-retention');
    const removed = await purgeOldForecastCache(new Date('2026-03-30T12:00:00.000Z'));

    expect(removed).toBe(22);
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('rm_forecast_accuracy_v2');
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('fa.forecast_id = fc.id');
    expect(mocked.poolQuery.mock.calls[0]?.[1]).toEqual(['2026-03-30T12:00:00.000Z', '30']);
  });

  it('respects retention overrides when deleting ended events', async () => {
    process.env.EVENTS_RETENTION_DAYS = '45';
    mocked.poolQuery.mockResolvedValueOnce({ rows: [{ count: 5 }] });

    const { purgeOldEndedEvents } = await import('../data-retention');
    const removed = await purgeOldEndedEvents(new Date('2026-03-30T12:00:00.000Z'));

    expect(removed).toBe(5);
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain("e.status = 'ended'");
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('FROM rm_user_picks');
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('FROM rm_forecast_cache');
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('FROM rm_archived_forecasts');
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('FROM rm_forecast_precomputed');
    expect(mocked.poolQuery.mock.calls[0]?.[1]).toEqual(['2026-03-30T12:00:00.000Z', '45']);
  });

  it('runs all retention stages in order', async () => {
    mocked.poolQuery
      .mockResolvedValueOnce({ rows: [{ count: 3 }] })
      .mockResolvedValueOnce({ rows: [{ count: 2 }] })
      .mockResolvedValueOnce({ rows: [{ count: 1 }] });

    const { runDataRetention } = await import('../data-retention');
    const result = await runDataRetention(new Date('2026-03-30T12:00:00.000Z'));

    expect(result).toEqual({
      expiredPrecomputed: 3,
      oldForecastCache: 2,
      oldEndedEvents: 1,
    });
    expect(mocked.poolQuery).toHaveBeenCalledTimes(3);
  });
});
