import { describe, it, expect, vi, beforeEach } from 'vitest';
import { GET as getConversations, DELETE as deleteConversation } from '@/app/api/token/conversations/route';
import { GET as getConversation } from '@/app/api/token/conversations/[id]/route';
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';

// Mock database
vi.mock('@/lib/db', () => ({
  db: {
    tokenConversation: {
      findMany: vi.fn(),
      findFirst: vi.fn(),
      delete: vi.fn(),
    },
  },
}));

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

// Mock logger
vi.mock('@/lib/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

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

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

  it('should return list of conversations with message counts', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);

    const mockConversations = [
      {
        id: 'conv_1',
        title: 'First conversation',
        createdAt: new Date('2026-02-20'),
        updatedAt: new Date('2026-02-21'),
        _count: { messages: 5 },
      },
      {
        id: 'conv_2',
        title: 'Second conversation',
        createdAt: new Date('2026-02-19'),
        updatedAt: new Date('2026-02-20'),
        _count: { messages: 3 },
      },
    ];
    vi.mocked(db.tokenConversation.findMany).mockResolvedValue(mockConversations as any);

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.conversations).toHaveLength(2);
    expect(data.conversations[0].id).toBe('conv_1');
    expect(data.conversations[0].title).toBe('First conversation');
    expect(data.conversations[0].messageCount).toBe(5);
    expect(data.conversations[1].messageCount).toBe(3);
  });

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

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

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

  it('should return empty array if user has no conversations', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    vi.mocked(db.tokenConversation.findMany).mockResolvedValue([]);

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.conversations).toHaveLength(0);
  });

  it('should query with correct filters and ordering', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    vi.mocked(db.tokenConversation.findMany).mockResolvedValue([]);

    // Act
    await getConversations();

    // Assert
    expect(db.tokenConversation.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { userId: 'user_1' },
        orderBy: { updatedAt: 'desc' },
        take: 50,
      }),
    );
  });
});

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

  it('should delete a conversation owned by the user', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);

    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue({
      id: 'conv_1',
      userId: 'user_1',
    } as any);
    vi.mocked(db.tokenConversation.delete).mockResolvedValue({} as any);

    const request = new NextRequest(
      'http://localhost:3000/api/token/conversations?id=conv_1',
      { method: 'DELETE' },
    );

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.deleted).toBe(true);
    expect(db.tokenConversation.delete).toHaveBeenCalledWith({ where: { id: 'conv_1' } });
  });

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

    const request = new NextRequest(
      'http://localhost:3000/api/token/conversations?id=conv_1',
      { method: 'DELETE' },
    );

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

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

  it('should return 400 if id parameter is missing', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);

    const request = new NextRequest(
      'http://localhost:3000/api/token/conversations',
      { method: 'DELETE' },
    );

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.error).toContain('id');
  });

  it('should return 404 for conversation not owned by user', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);

    // findFirst returns null because query filters by userId
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    const request = new NextRequest(
      'http://localhost:3000/api/token/conversations?id=conv_other',
      { method: 'DELETE' },
    );

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

    // Assert
    expect(response.status).toBe(404);
    expect(data.error).toContain('not found');
    expect(db.tokenConversation.delete).not.toHaveBeenCalled();
  });

  it('should verify ownership by querying with userId filter', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    const request = new NextRequest(
      'http://localhost:3000/api/token/conversations?id=conv_1',
      { method: 'DELETE' },
    );

    // Act
    await deleteConversation(request);

    // Assert
    expect(db.tokenConversation.findFirst).toHaveBeenCalledWith({
      where: { id: 'conv_1', userId: 'user_1' },
    });
  });
});

describe('GET /api/token/conversations/:id', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should return conversation with messages', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);

    const mockConversation = {
      id: 'conv_1',
      title: 'Test conversation',
      createdAt: new Date('2026-02-20'),
      updatedAt: new Date('2026-02-21'),
      messages: [
        {
          id: 'msg_1',
          role: 'user',
          content: 'Hello',
          model: null,
          costUsd: null,
          latencyMs: null,
          createdAt: new Date('2026-02-20T10:00:00'),
        },
        {
          id: 'msg_2',
          role: 'assistant',
          content: 'Hi there',
          model: 'google/gemini-2.5-flash',
          costUsd: 0.001,
          latencyMs: 1500,
          createdAt: new Date('2026-02-20T10:00:02'),
        },
      ],
    };
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(mockConversation as any);

    // Act
    const response = await getConversation(
      new NextRequest('http://localhost:3000/api/token/conversations/conv_1'),
      { params: Promise.resolve({ id: 'conv_1' }) },
    );
    const data = await response.json();

    // Assert
    expect(response.status).toBe(200);
    expect(data.conversation.id).toBe('conv_1');
    expect(data.conversation.title).toBe('Test conversation');
    expect(data.conversation.messages).toHaveLength(2);
    expect(data.conversation.messages[0].role).toBe('user');
    expect(data.conversation.messages[1].role).toBe('assistant');
  });

  it('should return 404 if conversation not found', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    // Act
    const response = await getConversation(
      new NextRequest('http://localhost:3000/api/token/conversations/nonexistent'),
      { params: Promise.resolve({ id: 'nonexistent' }) },
    );
    const data = await response.json();

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

  it('should return 404 if conversation belongs to different user', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    // findFirst filters by userId, returns null for wrong owner
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue(null);

    // Act
    const response = await getConversation(
      new NextRequest('http://localhost:3000/api/token/conversations/conv_other'),
      { params: Promise.resolve({ id: 'conv_other' }) },
    );
    const data = await response.json();

    // Assert
    expect(response.status).toBe(404);
    expect(data.error).toContain('not found');
    expect(db.tokenConversation.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'conv_other', userId: 'user_1' },
      }),
    );
  });

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

    // Act
    const response = await getConversation(
      new NextRequest('http://localhost:3000/api/token/conversations/conv_1'),
      { params: Promise.resolve({ id: 'conv_1' }) },
    );
    const data = await response.json();

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

  it('should query with correct select fields and message ordering', async () => {
    // Arrange
    vi.mocked(auth).mockResolvedValue({
      user: { id: 'user_1', email: 'test@test.com' },
    } as any);
    vi.mocked(db.tokenConversation.findFirst).mockResolvedValue({
      id: 'conv_1',
      title: 'test',
      createdAt: new Date(),
      updatedAt: new Date(),
      messages: [],
    } as any);

    // Act
    await getConversation(
      new NextRequest('http://localhost:3000/api/token/conversations/conv_1'),
      { params: Promise.resolve({ id: 'conv_1' }) },
    );

    // Assert
    expect(db.tokenConversation.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'conv_1', userId: 'user_1' },
        select: expect.objectContaining({
          id: true,
          title: true,
          messages: expect.objectContaining({
            orderBy: { createdAt: 'asc' },
          }),
        }),
      }),
    );
  });
});
