import { describe, it, expect, beforeEach, vi } from 'vitest';
import { GET, POST } from '@/app/api/feedback/route';
import { GET as GET_STATS } from '@/app/api/feedback/stats/route';
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';

// Mock the database
vi.mock('@/lib/db', () => ({
  db: {
    feedback: {
      findMany: vi.fn(),
      count: vi.fn(),
      create: vi.fn(),
    },
  },
}));

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

import { auth } from '@/lib/auth';

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

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

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'GET',
      });

      const response = await GET(request);
      const data = await response.json();

      expect(response.status).toBe(401);
      expect(data.error).toBe('Unauthorized');
      expect(db.feedback.findMany).not.toHaveBeenCalled();
    });

    it('should return feedback entries for authenticated user', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const mockFeedback = [
        {
          id: 'fb-1',
          userId: 'user-1',
          requestId: 'req-123',
          rating: 5,
          thumbsUp: true,
          comment: 'Great response',
          model: 'gpt-4',
          createdAt: new Date(),
        },
        {
          id: 'fb-2',
          userId: 'user-1',
          requestId: null,
          rating: 2,
          thumbsUp: false,
          comment: null,
          model: null,
          createdAt: new Date(),
        },
      ];

      vi.mocked(db.feedback.findMany).mockResolvedValue(mockFeedback as any);
      vi.mocked(db.feedback.count).mockResolvedValue(2);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'GET',
      });

      const response = await GET(request);
      const data = await response.json();

      expect(response.status).toBe(200);
      expect(data.feedback).toHaveLength(2);
      expect(data.total).toBe(2);
      expect(db.feedback.findMany).toHaveBeenCalledWith({
        where: { userId: 'user-1' },
        orderBy: { createdAt: 'desc' },
        take: 50,
        skip: 0,
      });
    });

    it('should respect limit and offset params', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      vi.mocked(db.feedback.findMany).mockResolvedValue([]);
      vi.mocked(db.feedback.count).mockResolvedValue(0);

      const request = new NextRequest(
        'http://localhost:3000/api/feedback?limit=10&offset=20',
        { method: 'GET' }
      );

      const response = await GET(request);

      expect(response.status).toBe(200);
      expect(db.feedback.findMany).toHaveBeenCalledWith({
        where: { userId: 'user-1' },
        orderBy: { createdAt: 'desc' },
        take: 10,
        skip: 20,
      });
    });

    it('should cap limit at 100', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      vi.mocked(db.feedback.findMany).mockResolvedValue([]);
      vi.mocked(db.feedback.count).mockResolvedValue(0);

      const request = new NextRequest(
        'http://localhost:3000/api/feedback?limit=500',
        { method: 'GET' }
      );

      const response = await GET(request);

      expect(response.status).toBe(200);
      expect(db.feedback.findMany).toHaveBeenCalledWith(
        expect.objectContaining({ take: 100 })
      );
    });

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

      vi.mocked(db.feedback.findMany).mockRejectedValue(new Error('DB error'));

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'GET',
      });

      const response = await GET(request);
      const data = await response.json();

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

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

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true }),
      });

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

      expect(response.status).toBe(401);
      expect(data.error).toBe('Unauthorized');
      expect(db.feedback.create).not.toHaveBeenCalled();
    });

    it('should create feedback with thumbsUp only', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const mockFeedback = {
        id: 'fb-1',
        userId: 'user-1',
        requestId: null,
        rating: null,
        thumbsUp: true,
        comment: null,
        model: null,
        createdAt: new Date(),
      };

      vi.mocked(db.feedback.create).mockResolvedValue(mockFeedback as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true }),
      });

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

      expect(response.status).toBe(201);
      expect(data.feedback.thumbsUp).toBe(true);
      expect(db.feedback.create).toHaveBeenCalledWith({
        data: {
          userId: 'user-1',
          requestId: null,
          rating: null,
          thumbsUp: true,
          comment: null,
          model: null,
        },
      });
    });

    it('should create feedback with all fields', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const mockFeedback = {
        id: 'fb-1',
        userId: 'user-1',
        requestId: 'req-123',
        rating: 4,
        thumbsUp: true,
        comment: 'Good answer',
        model: 'claude-3',
        createdAt: new Date(),
      };

      vi.mocked(db.feedback.create).mockResolvedValue(mockFeedback as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({
          requestId: 'req-123',
          rating: 4,
          thumbsUp: true,
          comment: 'Good answer',
          model: 'claude-3',
        }),
      });

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

      expect(response.status).toBe(201);
      expect(data.feedback).toMatchObject({
        requestId: 'req-123',
        rating: 4,
        thumbsUp: true,
        comment: 'Good answer',
        model: 'claude-3',
      });
    });

    it('should reject when thumbsUp is missing', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ rating: 5 }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('thumbsUp must be a boolean');
      expect(db.feedback.create).not.toHaveBeenCalled();
    });

    it('should reject when thumbsUp is not a boolean', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: 'yes' }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('thumbsUp must be a boolean');
    });

    it('should reject rating outside 1-5 range', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true, rating: 6 }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('Rating must be an integer between 1 and 5');
    });

    it('should reject rating of 0', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true, rating: 0 }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('Rating must be an integer between 1 and 5');
    });

    it('should reject non-integer rating', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true, rating: 3.5 }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('Rating must be an integer between 1 and 5');
    });

    it('should reject comment over 1000 characters', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({
          thumbsUp: true,
          comment: 'x'.repeat(1001),
        }),
      });

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

      expect(response.status).toBe(400);
      expect(data.error).toContain('1000 characters or less');
    });

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

      vi.mocked(db.feedback.create).mockRejectedValue(new Error('DB error'));

      const request = new NextRequest('http://localhost:3000/api/feedback', {
        method: 'POST',
        body: JSON.stringify({ thumbsUp: true }),
      });

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

      expect(response.status).toBe(500);
      expect(data.error).toBe('Failed to create feedback');
    });
  });

  describe('GET /api/feedback/stats', () => {
    it('should reject unauthenticated requests', async () => {
      vi.mocked(auth).mockResolvedValue(null as any);

      const response = await GET_STATS();
      const data = await response.json();

      expect(response.status).toBe(401);
      expect(data.error).toBe('Unauthorized');
    });

    it('should return feedback statistics', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      // Mock the parallel queries
      vi.mocked(db.feedback.count)
        .mockResolvedValueOnce(10)  // total
        .mockResolvedValueOnce(7)   // thumbsUp true
        .mockResolvedValueOnce(3);  // thumbsUp false

      vi.mocked(db.feedback.findMany).mockResolvedValue([
        { rating: 5 },
        { rating: 4 },
        { rating: 4 },
        { rating: 3 },
        { rating: 5 },
      ] as any);

      const response = await GET_STATS();
      const data = await response.json();

      expect(response.status).toBe(200);
      expect(data.total).toBe(10);
      expect(data.thumbsUpCount).toBe(7);
      expect(data.thumbsDownCount).toBe(3);
      expect(data.averageRating).toBe(4.2);
      expect(data.ratedCount).toBe(5);
      expect(data.ratingDistribution).toEqual({
        '1': 0,
        '2': 0,
        '3': 1,
        '4': 2,
        '5': 2,
      });
    });

    it('should return null average when no ratings exist', async () => {
      vi.mocked(auth).mockResolvedValue({
        user: { id: 'user-1', email: 'test@example.com' },
      } as any);

      vi.mocked(db.feedback.count)
        .mockResolvedValueOnce(3)
        .mockResolvedValueOnce(2)
        .mockResolvedValueOnce(1);

      vi.mocked(db.feedback.findMany).mockResolvedValue([]);

      const response = await GET_STATS();
      const data = await response.json();

      expect(response.status).toBe(200);
      expect(data.total).toBe(3);
      expect(data.averageRating).toBeNull();
      expect(data.ratedCount).toBe(0);
    });

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

      vi.mocked(db.feedback.count).mockRejectedValue(new Error('DB error'));

      const response = await GET_STATS();
      const data = await response.json();

      expect(response.status).toBe(500);
      expect(data.error).toBe('Failed to fetch feedback stats');
    });
  });
});
