import { describe, it, expect, beforeEach, vi } from 'vitest';
import { GET, POST } from '@/app/api/keys/route';
import { DELETE } from '@/app/api/keys/[id]/route';
import { NextRequest } from 'next/server';
import { db } from '@/lib/db';

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

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

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

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

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

  it('should list all active API keys for authenticated user', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

    const mockKeys = [
      {
        id: 'key_1',
        name: 'Production Key',
        prefix: 'lh_sk_abc...',
        lastUsed: new Date('2026-02-10'),
        expiresAt: null,
        createdAt: new Date('2026-01-01'),
      },
      {
        id: 'key_2',
        name: 'Development Key',
        prefix: 'lh_sk_def...',
        lastUsed: null,
        expiresAt: null,
        createdAt: new Date('2026-01-15'),
      },
    ];

    vi.mocked(db.apiKey.findMany).mockResolvedValue(mockKeys as any);

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

    // Assert
    expect(response.status).toBe(200);
    expect(data.keys).toHaveLength(2);
    expect(data.keys[0].name).toBe('Production Key');
    expect(db.apiKey.findMany).toHaveBeenCalledWith({
      where: { userId: 'user_123', revokedAt: null },
      select: {
        id: true,
        name: true,
        prefix: true,
        lastUsed: true,
        expiresAt: true,
        createdAt: true,
      },
      orderBy: { createdAt: 'desc' },
    });
  });

  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.apiKey.findMany).not.toHaveBeenCalled();
  });

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

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

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

  it('should filter out revoked keys', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findMany).mockResolvedValue([
      {
        id: 'key_1',
        name: 'Active Key',
        prefix: 'lh_sk_abc...',
        lastUsed: null,
        expiresAt: null,
        createdAt: new Date(),
      },
    ] as any);

    // Act
    await GET();

    // Assert
    expect(db.apiKey.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { userId: 'user_123', revokedAt: null },
      })
    );
  });

  it('should handle database errors gracefully', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findMany).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 API keys');
  });
});

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

  it('should create a new API key with valid data', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(0);
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'free' } as any);

    const mockApiKey = {
      id: 'key_1',
      name: 'Test Key',
      key: 'lh_sk_mockbase64urlencoded123456789',
      prefix: 'lh_sk_mockba...',
      createdAt: new Date(),
    };
    vi.mocked(db.apiKey.create).mockResolvedValue(mockApiKey as any);

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

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

    // Assert
    expect(response.status).toBe(201);
    expect(data.id).toBe('key_1');
    expect(data.name).toBe('Test Key');
    expect(data.key).toMatch(/^lh_sk_/);
    expect(data.prefix).toBe('lh_sk_mockba...');
    expect(db.apiKey.create).toHaveBeenCalledWith({
      data: {
        userId: 'user_123',
        name: 'Test Key',
        key: expect.stringMatching(/^lh_sk_/),
        prefix: 'lh_sk_mockba...',
      },
    });
  });

  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/keys', {
      method: 'POST',
      body: JSON.stringify({ name: 'Test Key' }),
    });

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

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

  it('should reject creation without name', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

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

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.error).toBe('Name is required');
    expect(db.apiKey.create).not.toHaveBeenCalled();
  });

  it('should reject creation with empty name', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

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

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.error).toBe('Name is required');
    expect(db.apiKey.create).not.toHaveBeenCalled();
  });

  it('should reject creation with name > 64 characters', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

    const longName = 'a'.repeat(65);
    const request = new NextRequest('http://localhost:3000/api/keys', {
      method: 'POST',
      body: JSON.stringify({ name: longName }),
    });

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

    // Assert
    expect(response.status).toBe(400);
    expect(data.error).toBe('Name must be 64 characters or less');
    expect(db.apiKey.create).not.toHaveBeenCalled();
  });

  it('should enforce free plan limit (2 keys)', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(2); // Already at limit
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'free' } as any);

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

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

    // Assert
    expect(response.status).toBe(403);
    expect(data.error).toContain('API key limit reached');
    expect(data.error).toContain('2 keys');
    expect(data.error).toContain('free plan');
    expect(db.apiKey.create).not.toHaveBeenCalled();
  });

  it('should enforce pro plan limit (10 keys)', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(10); // At pro limit
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'pro' } as any);

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

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

    // Assert
    expect(response.status).toBe(403);
    expect(data.error).toContain('API key limit reached');
    expect(data.error).toContain('10 keys');
    expect(data.error).toContain('pro plan');
    expect(db.apiKey.create).not.toHaveBeenCalled();
  });

  it('should allow creation under team plan limit (50 keys)', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(30); // Under team limit
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'team' } as any);

    const mockApiKey = {
      id: 'key_1',
      name: 'Test Key',
      key: 'lh_sk_mockbase64urlencoded123456789',
      prefix: 'lh_sk_mockba...',
      createdAt: new Date(),
    };
    vi.mocked(db.apiKey.create).mockResolvedValue(mockApiKey as any);

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

    // Act
    const response = await POST(request);

    // Assert
    expect(response.status).toBe(201);
    expect(db.apiKey.create).toHaveBeenCalled();
  });

  it('should allow creation under enterprise plan limit (100 keys)', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(75); // Under enterprise limit
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'enterprise' } as any);

    const mockApiKey = {
      id: 'key_1',
      name: 'Test Key',
      key: 'lh_sk_mockbase64urlencoded123456789',
      prefix: 'lh_sk_mockba...',
      createdAt: new Date(),
    };
    vi.mocked(db.apiKey.create).mockResolvedValue(mockApiKey as any);

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

    // Act
    const response = await POST(request);

    // Assert
    expect(response.status).toBe(201);
    expect(db.apiKey.create).toHaveBeenCalled();
  });

  it('should generate key with lh_sk_ prefix', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(0);
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'free' } as any);

    const mockApiKey = {
      id: 'key_1',
      name: 'Test Key',
      key: 'lh_sk_mockbase64urlencoded123456789',
      prefix: 'lh_sk_mockba...',
      createdAt: new Date(),
    };
    vi.mocked(db.apiKey.create).mockResolvedValue(mockApiKey as any);

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

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

    // Assert
    expect(data.key).toMatch(/^lh_sk_/);
    expect(data.prefix).toMatch(/^lh_sk_/);
    expect(data.prefix).toContain('...');
  });

  it('should trim whitespace from name', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(0);
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'free' } as any);

    const mockApiKey = {
      id: 'key_1',
      name: 'Test Key',
      key: 'lh_sk_mockbase64urlencoded123456789',
      prefix: 'lh_sk_mockba...',
      createdAt: new Date(),
    };
    vi.mocked(db.apiKey.create).mockResolvedValue(mockApiKey as any);

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

    // Act
    await POST(request);

    // Assert
    expect(db.apiKey.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          name: 'Test Key',
        }),
      })
    );
  });

  it('should only count non-revoked keys for limit', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockResolvedValue(1); // Only active keys
    vi.mocked(db.user.findUnique).mockResolvedValue({ plan: 'free' } as any);

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

    // Act
    await POST(request);

    // Assert
    expect(db.apiKey.count).toHaveBeenCalledWith({
      where: { userId: 'user_123', revokedAt: null },
    });
  });

  it('should handle database errors gracefully', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.count).mockRejectedValue(new Error('Database error'));

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

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

    // Assert
    expect(response.status).toBe(500);
    expect(data.error).toBe('Failed to create API key');
  });
});

describe('/api/keys/[id] - DELETE', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should revoke an API key for authenticated user', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

    const mockApiKey = {
      id: 'key_1',
      userId: 'user_123',
      name: 'Test Key',
      revokedAt: null,
    };
    vi.mocked(db.apiKey.findFirst).mockResolvedValue(mockApiKey as any);
    vi.mocked(db.apiKey.update).mockResolvedValue({} as any);

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

    const params = Promise.resolve({ id: 'key_1' });

    // Act
    const response = await DELETE(request, { params });
    const data = await response.json();

    // Assert
    expect(response.status).toBe(200);
    expect(data.message).toBe('API key revoked');
    expect(db.apiKey.update).toHaveBeenCalledWith({
      where: { id: 'key_1' },
      data: { revokedAt: expect.any(Date) },
    });
  });

  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/keys/key_1', {
      method: 'DELETE',
    });

    const params = Promise.resolve({ id: 'key_1' });

    // Act
    const response = await DELETE(request, { params });
    const data = await response.json();

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

  it('should return 404 if key does not exist', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findFirst).mockResolvedValue(null);

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

    const params = Promise.resolve({ id: 'nonexistent' });

    // Act
    const response = await DELETE(request, { params });
    const data = await response.json();

    // Assert
    expect(response.status).toBe(404);
    expect(data.error).toBe('API key not found');
    expect(db.apiKey.update).not.toHaveBeenCalled();
  });

  it('should return 404 if key belongs to different user', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findFirst).mockResolvedValue(null); // Query filters by userId

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

    const params = Promise.resolve({ id: 'key_1' });

    // Act
    const response = await DELETE(request, { params });

    // Assert
    expect(response.status).toBe(404);
    expect(db.apiKey.findFirst).toHaveBeenCalledWith({
      where: { id: 'key_1', userId: 'user_123', revokedAt: null },
    });
  });

  it('should return 404 if key is already revoked', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findFirst).mockResolvedValue(null); // Query filters revokedAt: null

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

    const params = Promise.resolve({ id: 'key_1' });

    // Act
    const response = await DELETE(request, { params });

    // Assert
    expect(response.status).toBe(404);
    expect(db.apiKey.findFirst).toHaveBeenCalledWith({
      where: { id: 'key_1', userId: 'user_123', revokedAt: null },
    });
  });

  it('should use soft delete (set revokedAt timestamp)', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);

    const mockApiKey = {
      id: 'key_1',
      userId: 'user_123',
      name: 'Test Key',
      revokedAt: null,
    };
    vi.mocked(db.apiKey.findFirst).mockResolvedValue(mockApiKey as any);
    vi.mocked(db.apiKey.update).mockResolvedValue({} as any);

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

    const params = Promise.resolve({ id: 'key_1' });
    const beforeRevoke = new Date();

    // Act
    await DELETE(request, { params });

    // Assert
    const updateCall = vi.mocked(db.apiKey.update).mock.calls[0][0];
    const revokedAt = updateCall.data.revokedAt as Date;
    expect(revokedAt).toBeInstanceOf(Date);
    expect(revokedAt.getTime()).toBeGreaterThanOrEqual(beforeRevoke.getTime());
  });

  it('should handle database errors gracefully', async () => {
    // Arrange
    const mockSession = {
      user: { id: 'user_123', email: 'test@example.com' },
    };
    vi.mocked(auth).mockResolvedValue(mockSession as any);
    vi.mocked(db.apiKey.findFirst).mockRejectedValue(new Error('Database error'));

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

    const params = Promise.resolve({ id: 'key_1' });

    // Act
    const response = await DELETE(request, { params });
    const data = await response.json();

    // Assert
    expect(response.status).toBe(500);
    expect(data.error).toBe('Failed to revoke API key');
  });
});
