import { describe, it, expect, beforeEach, vi } from 'vitest';
import { GET, PATCH } from '@/app/api/user/route';
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';

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

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

describe('/api/user', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('GET', () => {
    it('should return user profile for authenticated user', async () => {
      // Arrange
      const mockUser = {
        id: '1',
        email: 'test@example.com',
        name: 'Test User',
        image: null,
        plan: 'free',
        createdAt: new Date('2024-01-01'),
      };

      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      vi.mocked(db.user.findUnique).mockResolvedValue(mockUser as any);

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

      // Assert
      expect(response.status).toBe(200);
      expect(data.user.id).toBe('1');
      expect(data.user.email).toBe('test@example.com');
      expect(data.user.name).toBe('Test User');
      expect(data.user.plan).toBe('free');
      expect(data.user.createdAt).toBe('2024-01-01T00:00:00.000Z'); // JSON serializes to string
      expect(db.user.findUnique).toHaveBeenCalledWith({
        where: { id: '1' },
        select: {
          id: true,
          email: true,
          name: true,
          image: true,
          plan: true,
          createdAt: true,
        },
      });
    });

    it('should return 401 if user is not authenticated', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue(null as any);

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

      // Assert
      expect(response.status).toBe(401);
      expect(data.error).toBe('Unauthorized');
      expect(db.user.findUnique).not.toHaveBeenCalled();
    });

    it('should return 404 if user is not found in database', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '999' },
      } as any);

      vi.mocked(db.user.findUnique).mockResolvedValue(null);

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

      // Assert
      expect(response.status).toBe(404);
      expect(data.error).toBe('User not found');
    });

    it('should return 500 if database query fails', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      vi.mocked(db.user.findUnique).mockRejectedValue(new Error('Database error'));

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

      // Assert
      expect(response.status).toBe(500);
      expect(data.error).toBe('Failed to fetch user');
    });
  });

  describe('PATCH', () => {
    it('should update user name successfully', async () => {
      // Arrange
      const updatedUser = {
        id: '1',
        email: 'test@example.com',
        name: 'Updated Name',
        image: null,
        plan: 'free',
        createdAt: new Date('2024-01-01'),
      };

      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      vi.mocked(db.user.update).mockResolvedValue(updatedUser as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({
          name: 'Updated Name',
        }),
      });

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

      // Assert
      expect(response.status).toBe(200);
      expect(data.user.name).toBe('Updated Name');
      expect(db.user.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: { name: 'Updated Name' },
        select: {
          id: true,
          email: true,
          name: true,
          image: true,
          plan: true,
          createdAt: true,
        },
      });
    });

    it('should return 401 if user is not authenticated', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue(null as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({ name: 'New Name' }),
      });

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

      // Assert
      expect(response.status).toBe(401);
      expect(data.error).toBe('Unauthorized');
      expect(db.user.update).not.toHaveBeenCalled();
    });

    it('should reject empty name', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({ name: '   ' }),
      });

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

      // Assert
      expect(response.status).toBe(400);
      expect(data.error).toBe('Name must be a non-empty string');
      expect(db.user.update).not.toHaveBeenCalled();
    });

    it('should reject non-string name', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({ name: 123 }),
      });

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

      // Assert
      expect(response.status).toBe(400);
      expect(data.error).toBe('Name must be a non-empty string');
      expect(db.user.update).not.toHaveBeenCalled();
    });

    it('should return error if no valid fields to update', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({}),
      });

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

      // Assert
      expect(response.status).toBe(400);
      expect(data.error).toBe('No valid fields to update');
      expect(db.user.update).not.toHaveBeenCalled();
    });

    it('should trim whitespace from name', async () => {
      // Arrange
      const updatedUser = {
        id: '1',
        email: 'test@example.com',
        name: 'Trimmed Name',
        image: null,
        plan: 'free',
        createdAt: new Date('2024-01-01'),
      };

      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      vi.mocked(db.user.update).mockResolvedValue(updatedUser as any);

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({ name: '  Trimmed Name  ' }),
      });

      // Act
      await PATCH(request);

      // Assert
      expect(db.user.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: { name: 'Trimmed Name' },
        select: expect.any(Object),
      });
    });

    it('should return 500 if database update fails', async () => {
      // Arrange
      vi.mocked(auth).mockResolvedValue({
        user: { id: '1' },
      } as any);

      vi.mocked(db.user.update).mockRejectedValue(new Error('Database error'));

      const request = new NextRequest('http://localhost:3000/api/user', {
        method: 'PATCH',
        body: JSON.stringify({ name: 'New Name' }),
      });

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

      // Assert
      expect(response.status).toBe(500);
      expect(data.error).toBe('Failed to update user');
    });
  });
});
