/**
 * Tests for /api/users (GET, POST) and /api/users/[id] (GET, PUT, DELETE)
 *
 * Routes use requireAdmin from auth-server for auth, and platform-client
 * functions (listUsers, getUserCounts, registerUser, getUser, updateUser,
 * deleteUser) for data — no direct Cosmos repositories.
 */

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

// ── Mocks ──────────────────────────────────────────────────────────────

const mockRequireAdmin = vi.fn();
vi.mock('@/lib/auth-server', () => ({
  requireAdmin: (...args: unknown[]) => mockRequireAdmin(...args),
  getCurrentUser: (...args: unknown[]) => mockRequireAdmin(...args),
  getCurrentUserFromRequest: (...args: unknown[]) => mockRequireAdmin(...args),
}));

const mockListUsers = vi.fn();
const mockGetUserCounts = vi.fn();
const mockRegisterUser = vi.fn();
const mockGetUser = vi.fn();
const mockUpdateUser = vi.fn();
const mockDeleteUser = vi.fn();
vi.mock('@/lib/platform-client', () => ({
  listUsers: (...args: unknown[]) => mockListUsers(...args),
  getUserCounts: (...args: unknown[]) => mockGetUserCounts(...args),
  registerUser: (...args: unknown[]) => mockRegisterUser(...args),
  getUser: (...args: unknown[]) => mockGetUser(...args),
  updateUser: (...args: unknown[]) => mockUpdateUser(...args),
  deleteUser: (...args: unknown[]) => mockDeleteUser(...args),
}));

vi.mock('@/lib/product-config', () => ({
  PRODUCT_ID: 'test-product',
  getRequestProductId: () => 'test-product',
}));

import { GET as listUsersGET, POST as createUserPOST } from '@/app/api/users/route';
import {
  GET as getUserGET,
  PUT as updateUserPUT,
  DELETE as deleteUserDELETE,
} from '@/app/api/users/[id]/route';

// ── Helpers ────────────────────────────────────────────────────────────

async function nr(url: string, opts?: RequestInit) {
  const { NextRequest } = await import('next/server');
  return new NextRequest(
    new Request(url, {
      headers: {
        Authorization: 'Bearer test',
        'Content-Type': 'application/json',
        ...opts?.headers,
      },
      ...opts,
    })
  );
}

const admin = { id: 'usr_a', email: 'a@example.com', name: 'Admin', role: 'admin' };

// ── GET /api/users ─────────────────────────────────────────────────────

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

  it('returns 401 when unauthenticated', async () => {
    mockRequireAdmin.mockResolvedValue(null);
    const res = await listUsersGET(await nr('http://localhost:3001/api/users'));
    expect(res.status).toBe(401);
  });

  it('returns users list with total and byPlan', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockListUsers.mockResolvedValue({ users: [{ id: 'u1', email: 'a@b.com' }] });
    mockGetUserCounts.mockResolvedValue({
      total: 42,
      byPlan: { free: 30, pro: 10, enterprise: 2 },
    });

    const res = await listUsersGET(await nr('http://localhost:3001/api/users'));
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.users).toHaveLength(1);
    expect(data.total).toBe(42);
    expect(data.byPlan.free).toBe(30);
  });

  it('passes limit and offset from query params', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockListUsers.mockResolvedValue({ users: [] });
    mockGetUserCounts.mockResolvedValue({ total: 0, byPlan: {} });

    await listUsersGET(await nr('http://localhost:3001/api/users?limit=10&offset=20'));
    expect(mockListUsers).toHaveBeenCalledWith('test', 10, 20);
  });
});

// ── POST /api/users ────────────────────────────────────────────────────

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

  it('returns 403 for non-admin users', async () => {
    mockRequireAdmin.mockResolvedValue(null);
    const res = await createUserPOST(
      await nr('http://localhost:3001/api/users', {
        method: 'POST',
        body: JSON.stringify({ email: 'new@example.com', name: 'New', password: 'pass123' }),
      })
    );
    expect(res.status).toBe(403);
  });

  it('returns 400 when required fields are missing', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    const res = await createUserPOST(
      await nr('http://localhost:3001/api/users', {
        method: 'POST',
        body: JSON.stringify({ email: 'new@example.com' }),
      })
    );
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain('required');
  });

  it('creates user with 201 when admin provides all fields', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockRegisterUser.mockResolvedValue({
      user: {
        id: 'usr_new',
        email: 'new@example.com',
        displayName: 'New User',
        role: 'user',
        plan: 'free',
      },
    });

    const res = await createUserPOST(
      await nr('http://localhost:3001/api/users', {
        method: 'POST',
        body: JSON.stringify({ email: 'new@example.com', name: 'New User', password: 'pass123' }),
      })
    );
    expect(res.status).toBe(201);
    const data = await res.json();
    expect(data.email).toBe('new@example.com');
  });
});

// ── GET /api/users/[id] ────────────────────────────────────────────────

describe('GET /api/users/[id]', () => {
  beforeEach(() => vi.clearAllMocks());

  it('returns 401 when unauthenticated', async () => {
    mockRequireAdmin.mockResolvedValue(null);
    const res = await getUserGET(await nr('http://localhost:3001/api/users/usr_1'), {
      params: Promise.resolve({ id: 'usr_1' }),
    });
    expect(res.status).toBe(401);
  });

  it('returns user data on success', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockGetUser.mockResolvedValue({ id: 'usr_1', email: 'user@example.com', role: 'user' });
    const res = await getUserGET(await nr('http://localhost:3001/api/users/usr_1'), {
      params: Promise.resolve({ id: 'usr_1' }),
    });
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.id).toBe('usr_1');
  });
});

// ── PUT /api/users/[id] ────────────────────────────────────────────────

describe('PUT /api/users/[id]', () => {
  beforeEach(() => vi.clearAllMocks());

  it('returns 403 for non-admin users', async () => {
    mockRequireAdmin.mockResolvedValue(null);
    const res = await updateUserPUT(
      await nr('http://localhost:3001/api/users/usr_1', {
        method: 'PUT',
        body: JSON.stringify({ name: 'Updated' }),
      }),
      { params: Promise.resolve({ id: 'usr_1' }) }
    );
    expect(res.status).toBe(403);
  });

  it('updates user and returns updated data', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockUpdateUser.mockResolvedValue({ id: 'usr_1', displayName: 'Updated Name', role: 'user' });
    const res = await updateUserPUT(
      await nr('http://localhost:3001/api/users/usr_1', {
        method: 'PUT',
        body: JSON.stringify({ name: 'Updated Name' }),
      }),
      { params: Promise.resolve({ id: 'usr_1' }) }
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.displayName).toBe('Updated Name');
  });
});

// ── DELETE /api/users/[id] ─────────────────────────────────────────────

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

  it('returns 403 for non-admin', async () => {
    mockRequireAdmin.mockResolvedValue(null);
    const res = await deleteUserDELETE(
      await nr('http://localhost:3001/api/users/usr_1', { method: 'DELETE' }),
      { params: Promise.resolve({ id: 'usr_1' }) }
    );
    expect(res.status).toBe(403);
  });

  it('deletes user successfully', async () => {
    mockRequireAdmin.mockResolvedValue(admin);
    mockDeleteUser.mockResolvedValue({ success: true });
    const res = await deleteUserDELETE(
      await nr('http://localhost:3001/api/users/usr_1', { method: 'DELETE' }),
      { params: Promise.resolve({ id: 'usr_1' }) }
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
  });
});
