import { describe, it, expect, beforeEach, vi } from 'vitest';

// Mock next/server
vi.mock('next/server', () => ({
  NextResponse: {
    json: (body, init) => ({
      _body: body,
      status: init?.status || 200,
      headers: init?.headers || {},
      json: () => Promise.resolve(body),
    }),
  },
}));

// Mock next/headers cookies
const mockCookieStore = {
  set: vi.fn(),
  get: vi.fn(),
  delete: vi.fn(),
};
vi.mock('next/headers', () => ({
  cookies: vi.fn(() => Promise.resolve(mockCookieStore)),
}));

// Mock auth modules
vi.mock('@/lib/auth/password', () => ({
  verifyPassword: vi.fn((pw, hash) => hash === 'hashed:' + pw),
}));

vi.mock('@/lib/auth/jwt', () => ({
  signToken: vi.fn(() => Promise.resolve('fake-jwt-token')),
}));

vi.mock('@/lib/auth/loginLimiter', () => ({
  checkLock: vi.fn(() => ({ locked: false })),
  recordFail: vi.fn(),
  recordSuccess: vi.fn(),
  getClientIp: vi.fn(() => '127.0.0.1'),
}));

// Mock DB repos
const mockFindByUsername = vi.fn();
const mockGetPasswordHash = vi.fn();

vi.mock('@/lib/db/repos/usersRepo', () => ({
  findByUsername: (...args) => mockFindByUsername(...args),
  getPasswordHash: (...args) => mockGetPasswordHash(...args),
}));

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

  it('logs in successfully with valid credentials', async () => {
    mockFindByUsername.mockResolvedValue({ id: 'u1', username: 'testuser', role: 'user', is_active: true, is_approved: true });
    mockGetPasswordHash.mockResolvedValue('hashed:correctpw');

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'testuser', password: 'correctpw' }) };
    const res = await POST(req);

    expect(res.status).toBe(200);
    expect(res._body.user.username).toBe('testuser');
    expect(res._body.user.role).toBe('user');
  });

  it('returns 400 when username or password missing', async () => {
    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: '', password: '' }) };
    const res = await POST(req);

    expect(res.status).toBe(400);
    expect(res._body.error).toBe('Username and password are required');
  });

  it('returns 401 for non-existent user', async () => {
    mockFindByUsername.mockResolvedValue(null);

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'ghost', password: 'anything' }) };
    const res = await POST(req);

    expect(res.status).toBe(401);
    expect(res._body.error).toBe('Invalid credentials');
  });

  it('returns 401 for wrong password', async () => {
    mockFindByUsername.mockResolvedValue({ id: 'u1', username: 'testuser', role: 'user', is_active: true, is_approved: true });
    mockGetPasswordHash.mockResolvedValue('hashed:correctpw');

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'testuser', password: 'wrongpw' }) };
    const res = await POST(req);

    expect(res.status).toBe(401);
  });

  it('returns 403 when account is disabled', async () => {
    mockFindByUsername.mockResolvedValue({ id: 'u1', username: 'disabled', role: 'user', is_active: false, is_approved: true });

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'disabled', password: 'anything' }) };
    const res = await POST(req);

    expect(res.status).toBe(403);
    expect(res._body.error).toBe('Account disabled');
  });

  it('returns 403 when account not approved', async () => {
    mockFindByUsername.mockResolvedValue({ id: 'u1', username: 'pending', role: 'user', is_active: true, is_approved: false });

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'pending', password: 'anything' }) };
    const res = await POST(req);

    expect(res.status).toBe(403);
    expect(res._body.error).toBe('Account pending approval');
  });

  it('sets auth_token cookie on success', async () => {
    mockFindByUsername.mockResolvedValue({ id: 'u1', username: 'testuser', role: 'user', is_active: true, is_approved: true });
    mockGetPasswordHash.mockResolvedValue('hashed:secretpw');
    mockCookieStore.set.mockClear();

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'testuser', password: 'secretpw' }) };
    await POST(req);

    expect(mockCookieStore.set).toHaveBeenCalledWith(
      'auth_token',
      'fake-jwt-token',
      expect.objectContaining({ httpOnly: true, path: '/' }),
    );
  });

  it('returns 500 on unexpected error', async () => {
    mockFindByUsername.mockRejectedValue(new Error('DB failure'));

    const { POST } = await import('./route.js');
    const req = { json: () => Promise.resolve({ username: 'x', password: 'x' }) };
    const res = await POST(req);

    expect(res.status).toBe(500);
    expect(res._body.error).toBe('Login failed');
  });
});
