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

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

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

// 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: {
    forgotPassword: { max: 3, windowSeconds: 3600 },
  },
}));

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

  it('should create a password reset token for existing user', async () => {
    // Arrange
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: '1',
      email: 'user@example.com',
    } as any);
    vi.mocked(db.passwordResetToken.deleteMany).mockResolvedValue({ count: 0 } as any);
    vi.mocked(db.passwordResetToken.create).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'user@example.com',
      }),
    });

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.message).toContain('If an account exists');
    expect(db.passwordResetToken.deleteMany).toHaveBeenCalledWith({
      where: { email: 'user@example.com' },
    });
    expect(db.passwordResetToken.create).toHaveBeenCalledWith({
      data: {
        email: 'user@example.com',
        token: 'mock_reset_token_67890',
        expires: expect.any(Date),
      },
    });
  });

  it('should prevent email enumeration by returning success for non-existent user', async () => {
    // Arrange
    vi.mocked(db.user.findUnique).mockResolvedValue(null); // User doesn't exist

    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'nonexistent@example.com',
      }),
    });

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.message).toContain('If an account exists');
    expect(db.passwordResetToken.create).not.toHaveBeenCalled();
  });

  it('should reject request with missing email', async () => {
    // Arrange
    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({}),
    });

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.message).toContain('Email is required');
    expect(db.user.findUnique).not.toHaveBeenCalled();
  });

  it('should delete old reset tokens before creating new one', async () => {
    // Arrange
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: '1',
      email: 'user@example.com',
    } as any);
    vi.mocked(db.passwordResetToken.deleteMany).mockResolvedValue({ count: 2 } as any);
    vi.mocked(db.passwordResetToken.create).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'user@example.com',
      }),
    });

    // Act
    await POST(request);

    // Assert
    expect(db.passwordResetToken.deleteMany).toHaveBeenCalledBefore(
      db.passwordResetToken.create as any
    );
  });

  it('should create token with 1 hour expiry', async () => {
    // Arrange
    vi.mocked(db.user.findUnique).mockResolvedValue({
      id: '1',
      email: 'user@example.com',
    } as any);
    vi.mocked(db.passwordResetToken.deleteMany).mockResolvedValue({ count: 0 } as any);
    vi.mocked(db.passwordResetToken.create).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'user@example.com',
      }),
    });

    // Act
    await POST(request);

    // Assert
    const createCall = vi.mocked(db.passwordResetToken.create).mock.calls[0][0];
    const expires = createCall.data.expires as Date;
    const now = new Date();
    const minutesDiff = (expires.getTime() - now.getTime()) / (1000 * 60);
    expect(minutesDiff).toBeGreaterThan(59);
    expect(minutesDiff).toBeLessThan(61);
  });

  it('should handle database errors gracefully', async () => {
    // Arrange
    vi.mocked(db.user.findUnique).mockRejectedValue(new Error('Database error'));

    const request = new NextRequest('http://localhost:3000/api/auth/forgot-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'user@example.com',
      }),
    });

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

    // Assert
    expect(response.status).toBe(500);
    expect(data.message).toContain('error occurred');
  });
});
