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

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

vi.mock('../../db', () => ({
  default: {
    query: mocked.poolQuery,
    connect: mocked.poolConnect,
  },
}));

vi.mock('../ledger', () => ({ recordLedgerEntry: mocked.recordLedgerEntry }));

describe('signup bonus forecast behavior', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('does not grant daily resets while signup bonus forecasts remain', async () => {
    mocked.poolQuery
      .mockResolvedValueOnce({
        rows: [{
          email_verified: true,
          last_reset_date_pacific: null,
          daily_free_forecasts: 0,
          signup_bonus_forecasts: 12,
          survey_bonus_forecasts: 0,
          grace_expires_at: null,
        }],
      })
      .mockResolvedValueOnce({
        rows: [{ 1: 1 }],
      });

    const { ensureDailyGrant } = await import('../user');
    await ensureDailyGrant('user-1');

    expect(mocked.poolQuery).toHaveBeenCalledTimes(2);
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('signup_bonus_forecasts');
    expect(String(mocked.poolQuery.mock.calls[1]?.[0])).toContain('FROM rm_survey_responses');
    expect(mocked.recordLedgerEntry).not.toHaveBeenCalled();
  });

  it('does not grant daily resets while survey bonus forecasts remain', async () => {
    mocked.poolQuery
      .mockResolvedValueOnce({
        rows: [{
          email_verified: true,
          last_reset_date_pacific: null,
          daily_free_forecasts: 0,
          signup_bonus_forecasts: 0,
          survey_bonus_forecasts: 9,
          grace_expires_at: null,
        }],
      })
      .mockResolvedValueOnce({
        rows: [{ 1: 1 }],
      });

    const { ensureDailyGrant } = await import('../user');
    await ensureDailyGrant('user-1');

    expect(mocked.poolQuery).toHaveBeenCalledTimes(2);
    expect(String(mocked.poolQuery.mock.calls[0]?.[0])).toContain('survey_bonus_forecasts');
    expect(mocked.recordLedgerEntry).not.toHaveBeenCalled();
  });

  it('spends signup bonus forecasts before paid single credits', async () => {
    const clientQuery = vi.fn()
      .mockResolvedValueOnce(undefined)
      .mockResolvedValueOnce({
        rows: [{
          daily_free_forecasts: 0,
          daily_pass_picks: 0,
          daily_pass_date: null,
          tourist_pass_expires_at: null,
          signup_bonus_forecasts: 4,
          survey_bonus_forecasts: 6,
          single_picks: 9,
        }],
      })
      .mockResolvedValueOnce(undefined)
      .mockResolvedValueOnce(undefined);
    const release = vi.fn();

    mocked.poolConnect.mockResolvedValue({
      query: clientQuery,
      release,
    });

    const { deductPick } = await import('../user');
    const result = await deductPick('user-2');

    expect(result).toEqual({ success: true, source: 'signup_bonus' });
    expect(String(clientQuery.mock.calls[1]?.[0])).toContain('signup_bonus_forecasts');
    expect(String(clientQuery.mock.calls[2]?.[0])).toContain('SET signup_bonus_forecasts = signup_bonus_forecasts - 1');
    expect(String(clientQuery.mock.calls[3]?.[0])).toBe('COMMIT');
    expect(release).toHaveBeenCalledTimes(1);
  });

  it('spends survey bonus forecasts after signup bonus and before daily or paid credits', async () => {
    const clientQuery = vi.fn()
      .mockResolvedValueOnce(undefined)
      .mockResolvedValueOnce({
        rows: [{
          daily_free_forecasts: 10,
          daily_pass_picks: 0,
          daily_pass_date: null,
          tourist_pass_expires_at: null,
          signup_bonus_forecasts: 0,
          survey_bonus_forecasts: 6,
          single_picks: 9,
        }],
      })
      .mockResolvedValueOnce(undefined)
      .mockResolvedValueOnce(undefined);
    const release = vi.fn();

    mocked.poolConnect.mockResolvedValue({
      query: clientQuery,
      release,
    });

    const { deductPick } = await import('../user');
    const result = await deductPick('user-3');

    expect(result).toEqual({ success: true, source: 'survey_bonus' });
    expect(String(clientQuery.mock.calls[2]?.[0])).toContain('SET survey_bonus_forecasts = survey_bonus_forecasts - 1');
    expect(String(clientQuery.mock.calls[3]?.[0])).toBe('COMMIT');
    expect(release).toHaveBeenCalledTimes(1);
  });
});
