import express from 'express';
import bcrypt from 'bcryptjs';
import request from 'supertest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocked = vi.hoisted(() => ({
  findUserByEmail: vi.fn(),
  findUserByGoogleSub: vi.fn(),
  createUser: vi.fn(),
  findUserById: vi.fn(),
  getPickBalance: vi.fn(),
  setVerificationToken: vi.fn(),
  verifyEmail: vi.fn(),
  findByVerificationToken: vi.fn(),
  linkGoogleAccount: vi.fn(),
  ensureDailyGrant: vi.fn(),
  updateAffiliateAttribution: vi.fn(),
  markAffiliateSignupTracked: vi.fn(),
  recordLoginIp: vi.fn(),
  isInGracePeriod: vi.fn(() => true),
  needsGraceVerification: vi.fn(() => false),
  updateCredentials: vi.fn(),
  getProfile: vi.fn(),
  setPasswordResetToken: vi.fn(),
  findByPasswordResetToken: vi.fn(),
  resetPassword: 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,
  findUserByEmail: mocked.findUserByEmail,
  findUserByGoogleSub: mocked.findUserByGoogleSub,
  createUser: mocked.createUser,
  findUserById: mocked.findUserById,
  getPickBalance: mocked.getPickBalance,
  setVerificationToken: mocked.setVerificationToken,
  verifyEmail: mocked.verifyEmail,
  findByVerificationToken: mocked.findByVerificationToken,
  linkGoogleAccount: mocked.linkGoogleAccount,
  ensureDailyGrant: mocked.ensureDailyGrant,
  updateAffiliateAttribution: mocked.updateAffiliateAttribution,
  markAffiliateSignupTracked: mocked.markAffiliateSignupTracked,
  recordLoginIp: mocked.recordLoginIp,
  isInGracePeriod: mocked.isInGracePeriod,
  needsGraceVerification: mocked.needsGraceVerification,
  updateCredentials: mocked.updateCredentials,
  getProfile: mocked.getProfile,
  setPasswordResetToken: mocked.setPasswordResetToken,
  findByPasswordResetToken: mocked.findByPasswordResetToken,
  resetPassword: mocked.resetPassword,
  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 quick-signup', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-secret';
    mocked.recordLoginIp.mockResolvedValue(undefined);
    mocked.setVerificationToken.mockResolvedValue(undefined);
    mocked.updateAffiliateAttribution.mockResolvedValue(undefined);
    mocked.markAffiliateSignupTracked.mockResolvedValue(undefined);
    mocked.getPickBalance.mockResolvedValue({
      single_picks: 0,
      signup_bonus_forecasts: 30,
      survey_bonus_forecasts: 0,
      daily_pass_picks: 0,
      daily_pass_valid: false,
      daily_free_forecasts: 0,
      email_verified: false,
      next_reset_at: '2026-03-29T06:00:00.000Z',
      tourist_pass_expires_at: null,
      tourist_pass_timezone: null,
    });
    mocked.setPasswordResetToken.mockResolvedValue(undefined);
    mocked.resetPassword.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);
  });

  it('returns a live temp session and grace metadata for a new quick-signup user', async () => {
    mocked.findUserByEmail.mockResolvedValue(null);
    mocked.createUser.mockResolvedValue({
      id: 'user-1',
      email: 'temp@example.com',
      is_weatherman: false,
      email_verified: false,
      grace_expires_at: '2026-03-30T12:00:00.000Z',
    });

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

    const res = await request(app)
      .post('/quick-signup')
      .send({ email: 'temp@example.com', adultConfirmed: true });

    expect(res.status).toBe(201);
    expect(String(res.headers['set-cookie'] ?? '')).toContain('rm_auth=');
    expect(String(res.headers['set-cookie'] ?? '')).toContain('rm_logged_in=');
    expect(res.body.message).toContain('30 free forecasts');
    expect(res.body.user).toMatchObject({
      id: 'user-1',
      email: 'temp@example.com',
      signup_bonus_forecasts: 30,
      survey_bonus_forecasts: 0,
      daily_free_forecasts: 0,
      grace_expires_at: '2026-03-30T12:00:00.000Z',
      in_grace_period: true,
      needs_grace_verification: false,
    });
    expect(mocked.trackTikTokLead).not.toHaveBeenCalled();
    expect(mocked.trackTikTokLeadSubmission).not.toHaveBeenCalled();
    expect(mocked.trackTikTokSubmitForm).not.toHaveBeenCalled();
  });

  it('forwards new quick-signups to TikTok when analytics consent exists', async () => {
    mocked.findUserByEmail.mockResolvedValue(null);
    mocked.createUser.mockResolvedValue({
      id: 'user-9',
      email: 'temp@example.com',
      is_weatherman: false,
      email_verified: false,
      grace_expires_at: '2026-03-30T12:00:00.000Z',
    });

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

    const res = await request(app)
      .post('/quick-signup')
      .set('Cookie', ['rm_cookie_consent=accepted', 'rm_ttclid=ttclid-123', '_ttp=ttp-cookie'])
      .send({
        email: 'temp@example.com',
        adultConfirmed: true,
        tracking: {
          consent: true,
          eventId: 'signup-123',
          placement: 'signup_gate',
          ttclid: 'ttclid-123',
          ttp: 'ttp-cookie',
          url: 'https://rainmakersports.app/signup',
        },
      });

    expect(res.status).toBe(201);
    expect(mocked.trackTikTokLeadSubmission).toHaveBeenCalledWith(expect.objectContaining({
      email: 'temp@example.com',
      eventId: 'signup-123_lead',
      ttclid: 'ttclid-123',
      ttp: 'ttp-cookie',
      description: 'signup_gate',
      url: 'https://rainmakersports.app/signup',
    }));
    expect(mocked.trackTikTokSubmitForm).toHaveBeenCalledWith(expect.objectContaining({
      email: 'temp@example.com',
      eventId: 'signup-123_submit_form',
      ttclid: 'ttclid-123',
      ttp: 'ttp-cookie',
      description: 'signup_gate',
      url: 'https://rainmakersports.app/signup',
    }));
    expect(mocked.trackTikTokLead).not.toHaveBeenCalled();
  });

  it('resends verification for an existing unverified user without creating a session', async () => {
    mocked.findUserByEmail.mockResolvedValue({
      id: 'user-2',
      email: 'retry@example.com',
      is_weatherman: false,
      email_verified: false,
      grace_expires_at: '2026-03-30T12:00:00.000Z',
    });

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

    const res = await request(app)
      .post('/quick-signup')
      .send({ email: 'retry@example.com' });

    expect(res.status).toBe(200);
    expect(res.headers['set-cookie']).toBeUndefined();
    expect(res.body).toEqual({
      authenticated: false,
      verificationSent: true,
      message: 'Verification email sent. Verify your email to continue.',
    });
    expect(mocked.setVerificationToken).toHaveBeenCalledWith(
      'user-2',
      expect.any(String),
      expect.any(Date),
    );
    expect(mocked.sendVerificationEmail).toHaveBeenCalledWith(
      'retry@example.com',
      expect.any(String),
    );
  });

  it('rejects quick-signup when adult confirmation is missing for a new account', async () => {
    mocked.findUserByEmail.mockResolvedValue(null);

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

    const res = await request(app)
      .post('/quick-signup')
      .send({ email: 'temp@example.com' });

    expect(res.status).toBe(400);
    expect(res.body.error).toContain('18 or older');
    expect(mocked.createUser).not.toHaveBeenCalled();
  });

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

    const res = await request(app)
      .post('/signup')
      .send({ email: 'fan@example.com', password: 'Password123' });

    expect(res.status).toBe(400);
    expect(res.body.error).toContain('18 or older');
    expect(mocked.findUserByEmail).not.toHaveBeenCalled();
    expect(mocked.createUser).not.toHaveBeenCalled();
  });

  it('returns a generic success message when requesting a password reset for an unknown email', async () => {
    mocked.findUserByEmail.mockResolvedValue(null);

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

    const res = await request(app)
      .post('/forgot-password')
      .send({ email: 'missing@example.com' });

    expect(res.status).toBe(200);
    expect(res.body.message).toContain('If that account exists');
    expect(mocked.setPasswordResetToken).not.toHaveBeenCalled();
  });

  it('stores a reset token and updates the password when a valid reset is submitted', async () => {
    mocked.findByPasswordResetToken.mockResolvedValue({
      id: 'user-3',
      email: 'reset@example.com',
      is_weatherman: false,
      email_verified: true,
    });

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

    const res = await request(app)
      .post('/reset-password')
      .send({ token: 'valid-token', password: 'NewPassword123' });

    expect(res.status).toBe(200);
    expect(res.body.message).toContain('Password updated');
    expect(mocked.resetPassword).toHaveBeenCalledWith(
      'user-3',
      expect.any(String),
    );
  });

  it('blocks public verification resends when the shared cooldown is active', async () => {
    mocked.findUserByEmail.mockResolvedValue({
      id: '00000000-0000-0000-0000-000000000123',
      email: 'retry@example.com',
      password_hash: bcrypt.hashSync('Password123', 4),
      email_verified: false,
    });
    mocked.reserveVerificationEmailCooldown.mockResolvedValue(null);

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

    const res = await request(app)
      .post('/resend-verification-public')
      .send({ email: 'retry@example.com', password: 'Password123' });

    expect(res.status).toBe(429);
    expect(res.body.error).toContain('60 seconds');
    expect(mocked.setVerificationToken).not.toHaveBeenCalled();
    expect(mocked.sendVerificationEmail).not.toHaveBeenCalled();
  });
});
