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

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

// Mock bcrypt
vi.mock('bcryptjs', () => ({
  default: {
    hash: vi.fn(() => Promise.resolve('new_hashed_password')),
  },
}));

// 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: {
    resetPassword: { max: 5, windowSeconds: 900 },
  },
}));

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

  it('should reset password with valid token', async () => {
    // Arrange
    const validToken = {
      id: '1',
      email: 'user@example.com',
      token: 'valid_token',
      expires: new Date(Date.now() + 60 * 60 * 1000), // 1 hour from now
    };

    vi.mocked(db.passwordResetToken.findUnique).mockResolvedValue(validToken as any);
    vi.mocked(db.user.update).mockResolvedValue({} as any);
    vi.mocked(db.passwordResetToken.delete).mockResolvedValue({} as any);

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

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.message).toContain('Password reset successful');
    expect(db.user.update).toHaveBeenCalledWith({
      where: { email: 'user@example.com' },
      data: { passwordHash: 'new_hashed_password' },
    });
    expect(db.passwordResetToken.delete).toHaveBeenCalledWith({
      where: { id: '1' },
    });
  });

  it('should reject with missing required fields', async () => {
    // Arrange
    const request = new NextRequest('http://localhost:3000/api/auth/reset-password', {
      method: 'POST',
      body: JSON.stringify({
        email: 'user@example.com',
        // Missing token and password
      }),
    });

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

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

  it('should reject password shorter than 8 characters', async () => {
    // Arrange
    const request = new NextRequest('http://localhost:3000/api/auth/reset-password', {
      method: 'POST',
      body: JSON.stringify({
        token: 'valid_token',
        email: 'user@example.com',
        password: 'short',
      }),
    });

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.message).toContain('8 characters');
    expect(db.passwordResetToken.findUnique).not.toHaveBeenCalled();
  });

  it('should reject with invalid token', async () => {
    // Arrange
    vi.mocked(db.passwordResetToken.findUnique).mockResolvedValue(null);

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

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.message).toContain('Invalid or expired');
    expect(db.user.update).not.toHaveBeenCalled();
  });

  it('should reject with expired token', async () => {
    // Arrange
    const expiredToken = {
      id: '1',
      email: 'user@example.com',
      token: 'expired_token',
      expires: new Date(Date.now() - 60 * 60 * 1000), // 1 hour ago
    };

    vi.mocked(db.passwordResetToken.findUnique).mockResolvedValue(expiredToken as any);
    vi.mocked(db.passwordResetToken.delete).mockResolvedValue({} as any);

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

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.message).toContain('expired');
    expect(db.user.update).not.toHaveBeenCalled();
    expect(db.passwordResetToken.delete).toHaveBeenCalled(); // Cleanup expired token
  });

  it('should delete used token after successful reset', async () => {
    // Arrange
    const validToken = {
      id: '1',
      email: 'user@example.com',
      token: 'valid_token',
      expires: new Date(Date.now() + 60 * 60 * 1000),
    };

    vi.mocked(db.passwordResetToken.findUnique).mockResolvedValue(validToken as any);
    vi.mocked(db.user.update).mockResolvedValue({} as any);
    vi.mocked(db.passwordResetToken.delete).mockResolvedValue({} as any);

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

    // Act
    await POST(request);

    // Assert
    expect(db.user.update).toHaveBeenCalledBefore(db.passwordResetToken.delete as any);
    expect(db.passwordResetToken.delete).toHaveBeenCalledWith({
      where: { id: '1' },
    });
  });

  it('should handle case-insensitive email matching', async () => {
    // Arrange
    const validToken = {
      id: '1',
      email: 'user@example.com',
      token: 'valid_token',
      expires: new Date(Date.now() + 60 * 60 * 1000),
    };

    vi.mocked(db.passwordResetToken.findUnique).mockResolvedValue(validToken as any);
    vi.mocked(db.user.update).mockResolvedValue({} as any);
    vi.mocked(db.passwordResetToken.delete).mockResolvedValue({} as any);

    const request = new NextRequest('http://localhost:3000/api/auth/reset-password', {
      method: 'POST',
      body: JSON.stringify({
        token: 'valid_token',
        email: 'User@Example.com', // Mixed case
        password: 'newpassword123',
      }),
    });

    // Act
    await POST(request);

    // Assert
    expect(db.passwordResetToken.findUnique).toHaveBeenCalledWith({
      where: {
        email_token: {
          email: 'user@example.com', // Should be lowercased
          token: 'valid_token',
        },
      },
    });
  });

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

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

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

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