import { describe, it, expect, beforeEach, vi } from 'vitest';
import { POST } from '@/app/api/auth/resend-verification/route';
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';

// Mock auth
vi.mock('@/lib/auth', () => ({
  auth: vi.fn(),
}));

// Mock database
vi.mock('@/lib/db', () => ({
  db: {
    user: {
      findUnique: vi.fn(),
    },
    verificationToken: {
      deleteMany: vi.fn(),
      create: vi.fn(),
    },
  },
}));

// Mock rate limiter
vi.mock('@/lib/rate-limit', () => ({
  checkRateLimit: vi.fn(() => ({
    allowed: true,
    remaining: 10,
    resetAt: Date.now() + 900000,
  })),
  getClientIp: vi.fn(() => '127.0.0.1'),
  AUTH_RATE_LIMITS: {
    resendVerification: { max: 3, windowSeconds: 3600 },
  },
}));

// Mock crypto
vi.mock('crypto', () => ({
  default: {
    randomBytes: vi.fn(() => ({
      toString: () => 'new_mock_token_789',
    })),
  },
}));

// Mock email sending
vi.mock('@/lib/email/send', () => ({
  sendVerificationEmail: vi.fn(() => Promise.resolve({ success: true, messageId: 'mock_id' })),
}));

describe('/api/auth/resend-verification', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should resend verification email for authenticated unverified user', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: {
        email: 'test@example.com',
      },
    } as any);

    vi.mocked(db.user.findUnique).mockResolvedValue({
      emailVerified: null,
    } as any);

    vi.mocked(db.verificationToken.deleteMany).mockResolvedValue({} as any);
    vi.mocked(db.verificationToken.create).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/resend-verification', {
      method: 'POST',
    });

    // Act
    const response = await POST(request);
    const data = await response.json();

    // Assert
    expect(response.status).toBe(200);
    expect(data.message).toContain('sent successfully');
    expect(db.verificationToken.deleteMany).toHaveBeenCalledWith({
      where: { identifier: 'test@example.com' },
    });
    expect(db.verificationToken.create).toHaveBeenCalled();
  });

  it('should reject unauthenticated requests', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue(null as any);

    const request = new NextRequest('http://localhost:3000/api/auth/resend-verification', {
      method: 'POST',
    });

    // Act
    const response = await POST(request);
    const data = await response.json();

    // Assert
    expect(response.status).toBe(401);
    expect(data.error).toContain('Not authenticated');
    expect(db.verificationToken.create).not.toHaveBeenCalled();
  });

  it('should reject already verified users', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: {
        email: 'verified@example.com',
      },
    } as any);

    vi.mocked(db.user.findUnique).mockResolvedValue({
      emailVerified: new Date(),
    } as any);

    const request = new NextRequest('http://localhost:3000/api/auth/resend-verification', {
      method: 'POST',
    });

    // Act
    const response = await POST(request);
    const data = await response.json();

    // Assert
    expect(response.status).toBe(400);
    expect(data.error).toContain('already verified');
    expect(db.verificationToken.create).not.toHaveBeenCalled();
  });

  it('should delete old verification tokens before creating new one', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: {
        email: 'test@example.com',
      },
    } as any);

    vi.mocked(db.user.findUnique).mockResolvedValue({
      emailVerified: null,
    } as any);

    vi.mocked(db.verificationToken.deleteMany).mockResolvedValue({} as any);
    vi.mocked(db.verificationToken.create).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/resend-verification', {
      method: 'POST',
    });

    // Act
    await POST(request);

    // Assert
    const deleteManyCall = vi.mocked(db.verificationToken.deleteMany).mock.calls[0];
    const createCall = vi.mocked(db.verificationToken.create).mock.calls[0];

    expect(deleteManyCall).toBeDefined();
    expect(createCall).toBeDefined();
    // Ensure delete happens before create
    expect(vi.mocked(db.verificationToken.deleteMany)).toHaveBeenCalledBefore(
      vi.mocked(db.verificationToken.create)
    );
  });

  it('should handle email sending errors gracefully', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: {
        email: 'test@example.com',
      },
    } as any);

    vi.mocked(db.user.findUnique).mockResolvedValue({
      emailVerified: null,
    } as any);

    vi.mocked(db.verificationToken.deleteMany).mockResolvedValue({} as any);
    vi.mocked(db.verificationToken.create).mockResolvedValue({} as any);

    const { sendVerificationEmail } = await import('@/lib/email/send');
    vi.mocked(sendVerificationEmail).mockRejectedValue(new Error('Email service down'));

    const request = new NextRequest('http://localhost:3000/api/auth/resend-verification', {
      method: 'POST',
    });

    // Act
    const response = await POST(request);
    const data = await response.json();

    // Assert
    expect(response.status).toBe(500);
    expect(data.error).toContain('Failed to send email');
  });
});
