import express from 'express';
import cookieParser from 'cookie-parser';
import request from 'supertest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocked = vi.hoisted(() => ({
  createUser: vi.fn(),
  ensureDailyGrant: vi.fn(),
  findByPasswordResetToken: vi.fn(),
  findByVerificationToken: vi.fn(),
  findUserByEmail: vi.fn(),
  findUserByGoogleSub: vi.fn(),
  findUserById: vi.fn(),
  getPickBalance: vi.fn(),
  getProfile: vi.fn(),
  isInGracePeriod: vi.fn(() => false),
  linkGoogleAccount: vi.fn(),
  markAffiliateSignupTracked: vi.fn(),
  needsGraceVerification: vi.fn(() => false),
  recordLoginIp: vi.fn(),
  resetPassword: vi.fn(),
  setPasswordResetToken: vi.fn(),
  setVerificationToken: vi.fn(),
  updateAffiliateAttribution: vi.fn(),
  updateCredentials: vi.fn(),
  verifyEmail: vi.fn(),
  reserveVerificationEmailCooldown: vi.fn(),
  releaseVerificationEmailCooldown: vi.fn(),
  sendVerificationEmail: vi.fn(),
  trackTikTokLead: vi.fn(),
  trackTikTokLeadSubmission: vi.fn(),
  trackTikTokSubmitForm: vi.fn(),
}));

vi.mock('../../models/user', () => ({
  DAILY_FORECAST_GRANT: 10,
  NEW_ACCOUNT_FORECASTS: 30,
  SURVEY_BONUS_FORECASTS: 30,
  createUser: mocked.createUser,
  ensureDailyGrant: mocked.ensureDailyGrant,
  findByPasswordResetToken: mocked.findByPasswordResetToken,
  findByVerificationToken: mocked.findByVerificationToken,
  findUserByEmail: mocked.findUserByEmail,
  findUserByGoogleSub: mocked.findUserByGoogleSub,
  findUserById: mocked.findUserById,
  getPickBalance: mocked.getPickBalance,
  getProfile: mocked.getProfile,
  isInGracePeriod: mocked.isInGracePeriod,
  linkGoogleAccount: mocked.linkGoogleAccount,
  markAffiliateSignupTracked: mocked.markAffiliateSignupTracked,
  needsGraceVerification: mocked.needsGraceVerification,
  recordLoginIp: mocked.recordLoginIp,
  resetPassword: mocked.resetPassword,
  setPasswordResetToken: mocked.setPasswordResetToken,
  setVerificationToken: mocked.setVerificationToken,
  updateAffiliateAttribution: mocked.updateAffiliateAttribution,
  updateCredentials: mocked.updateCredentials,
  verifyEmail: mocked.verifyEmail,
  reserveVerificationEmailCooldown: mocked.reserveVerificationEmailCooldown,
  releaseVerificationEmailCooldown: mocked.releaseVerificationEmailCooldown,
}));

vi.mock('../../services/email', () => ({
  sendPasswordResetEmail: vi.fn(async () => undefined),
  sendVerificationEmail: mocked.sendVerificationEmail,
  sendWelcomeEmail: vi.fn(async () => undefined),
}));

vi.mock('../../services/affiliate', () => ({
  trackAffiliateConversion: vi.fn(async () => undefined),
}));

vi.mock('../../services/tiktok', () => ({
  trackTikTokLead: mocked.trackTikTokLead,
  trackTikTokLeadSubmission: mocked.trackTikTokLeadSubmission,
  trackTikTokSubmitForm: mocked.trackTikTokSubmitForm,
}));

describe('/auth google oauth', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.unstubAllGlobals();
    process.env.JWT_SECRET = 'test-secret';
    process.env.FRONTEND_URL = 'https://rainmakersports.app';
    process.env.API_URL = 'https://rainmakersports.app/api';
    process.env.GOOGLE_CLIENT_ID = 'google-client-id';
    process.env.GOOGLE_CLIENT_SECRET = 'google-client-secret';
    process.env.GOOGLE_REDIRECT_URI = 'https://rainmakersports.app/api/auth/google/callback';
    mocked.recordLoginIp.mockResolvedValue(undefined);
    mocked.ensureDailyGrant.mockResolvedValue(undefined);
    mocked.linkGoogleAccount.mockResolvedValue(undefined);
    mocked.updateAffiliateAttribution.mockResolvedValue(undefined);
    mocked.markAffiliateSignupTracked.mockResolvedValue(undefined);
    mocked.reserveVerificationEmailCooldown.mockResolvedValue('2026-03-30T12:00:00.000Z');
    mocked.releaseVerificationEmailCooldown.mockResolvedValue(undefined);
    mocked.sendVerificationEmail.mockResolvedValue(undefined);
    mocked.trackTikTokLead.mockResolvedValue(undefined);
    mocked.trackTikTokLeadSubmission.mockResolvedValue(undefined);
    mocked.trackTikTokSubmitForm.mockResolvedValue(undefined);
    mocked.getPickBalance.mockResolvedValue({
      single_picks: 0,
      signup_bonus_forecasts: 0,
      survey_bonus_forecasts: 0,
      daily_pass_picks: 0,
      daily_pass_valid: false,
      daily_free_forecasts: 1,
      email_verified: true,
      next_reset_at: '2026-03-29T06:00:00.000Z',
      tourist_pass_expires_at: null,
      tourist_pass_timezone: null,
    });
  });

  it('redirects to Google with the expected client and callback', async () => {
    const { default: router } = await import('../auth');
    const app = express();
    app.use(cookieParser());
    app.use('/api/auth', router);

    const res = await request(app).get('/api/auth/google/start?mode=signup&returnTo=%2Fforecasts%3Fwelcome%3Dtrue&adultConfirmed=1');

    expect(res.status).toBe(302);
    expect(res.headers.location).toContain('https://accounts.google.com/o/oauth2/v2/auth');
    expect(res.headers.location).toContain('client_id=google-client-id');
    expect(res.headers.location).toContain(encodeURIComponent('https://rainmakersports.app/api/auth/google/callback'));
    expect(String(res.headers['set-cookie'] ?? '')).toContain('rm_google_oauth_state=');
  });

  it('rejects Google signup start when adult confirmation is missing', async () => {
    const { default: router } = await import('../auth');
    const app = express();
    app.use(cookieParser());
    app.use('/api/auth', router);

    const res = await request(app).get('/api/auth/google/start?mode=signup&returnTo=%2Fforecasts%3Fwelcome%3Dtrue');

    expect(res.status).toBe(302);
    expect(res.headers.location).toContain('/auth/google?mode=signup');
    expect(res.headers.location).toContain('error=adult_confirmation_required');
    expect(String(res.headers['set-cookie'] ?? '')).not.toContain('rm_google_oauth_state=');
  });

  it('rejects Google callback when the browser state cookie is missing', async () => {
    const { default: router } = await import('../auth');
    const app = express();
    app.use(cookieParser());
    app.use('/api/auth', router);

    const startRes = await request(app).get('/api/auth/google/start?mode=signup&returnTo=%2Fforecasts%3Fwelcome%3Dtrue%26survey%3D1&adultConfirmed=1');
    const authUrl = new URL(startRes.headers.location);
    const state = authUrl.searchParams.get('state');

    const callbackRes = await request(app).get(`/api/auth/google/callback?code=test-code&state=${encodeURIComponent(state || '')}`);

    expect(callbackRes.status).toBe(302);
    expect(callbackRes.headers.location).toContain('/auth/google?mode=signup');
    expect(callbackRes.headers.location).toContain('error=invalid_state');
    expect(mocked.createUser).not.toHaveBeenCalled();
  });

  it('creates a verified user from Google callback and redirects with a JWT', async () => {
    vi.stubGlobal('fetch', vi.fn()
      .mockResolvedValueOnce({
        ok: true,
        json: async () => ({ access_token: 'access-token' }),
      })
      .mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          sub: 'google-sub-1',
          email: 'fan@example.com',
          email_verified: true,
          name: 'Fan User',
        }),
      }));

    mocked.findUserByGoogleSub.mockResolvedValue(null);
    mocked.findUserByEmail.mockResolvedValue(null);
    mocked.createUser.mockResolvedValue({
      id: 'user-1',
      email: 'fan@example.com',
      is_weatherman: false,
      auth_token_version: 0,
      email_verified: false,
    });
    mocked.findUserById.mockResolvedValue({
      id: 'user-1',
      email: 'fan@example.com',
      is_weatherman: false,
      auth_token_version: 0,
      email_verified: true,
    });

    const { default: router } = await import('../auth');
    const app = express();
    app.use(cookieParser());
    app.use('/api/auth', router);
    const agent = request.agent(app);

    const startRes = await agent.get('/api/auth/google/start?mode=signup&returnTo=%2Fforecasts%3Fwelcome%3Dtrue%26survey%3D1&adultConfirmed=1&eventId=signup-event-1&ttclid=ttclid-123&ttp=ttp-cookie&trackingUrl=https%3A%2F%2Frainmakersports.app%2Fsignup&trackingPlacement=signup_page&trackingConsent=1');
    const authUrl = new URL(startRes.headers.location);
    const state = authUrl.searchParams.get('state');

    const callbackRes = await agent.get(`/api/auth/google/callback?code=test-code&state=${encodeURIComponent(state || '')}`);

    expect(callbackRes.status).toBe(302);
    expect(mocked.createUser).toHaveBeenCalledWith('fan@example.com', expect.any(String), false);
    expect(mocked.linkGoogleAccount).toHaveBeenCalledWith('user-1', 'google-sub-1');
    expect(callbackRes.headers.location).toBe('https://rainmakersports.app/forecasts?welcome=true&survey=1');
    expect(String(callbackRes.headers['set-cookie'] ?? '')).toContain('rm_auth=');
    expect(String(callbackRes.headers['set-cookie'] ?? '')).toContain('rm_logged_in=');
    expect(mocked.trackTikTokLead).toHaveBeenCalledWith(expect.objectContaining({
      email: 'fan@example.com',
      eventId: 'signup-event-1',
      description: 'signup_page',
      ttclid: 'ttclid-123',
      ttp: 'ttp-cookie',
      url: 'https://rainmakersports.app/signup',
    }));
  });
});
