// Admin route permission gate tests — GAP-03 / Batch 6.
//
// Representative coverage of 3 admin routes:
//   - POST /api/admin/users   (getUserSession  → role check: admin | masteradmin)
//   - GET  /api/admin/audit   (getUserSession  → isAtLeastRole: admin)
//   - POST /api/admin/users/lock  (getUserSession → isAtLeastRole: admin)
//
// Pattern: mock @/lib/auth/permissions so getUserSession() returns the value
// we control, then assert the HTTP status the route returns.
//
// Note: owner routes were removed in Batch 2 — this file does not reference them.

import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { UserSession } from '@/lib/auth/permissions-shared'

// ─── Session fixtures ─────────────────────────────────────────────────────────

function makeSession(role: UserSession['role']): UserSession {
  return {
    userId:             'user-1',
    authUserId:         'auth-1',
    roleId:             'role-1',
    role,
    permissions:        [],
    displayName:        'Test User',
    firstName:          'Test',
    lastName:           'User',
    email:              'test@example.com',
    avatarUrl:          null,
    departmentId:       null,
    mustChangePassword: false,
    accountStatus:      'active',
  }
}

const SESSION_ADMIN:       UserSession = makeSession('admin')
const SESSION_CONSULTANT:  UserSession = makeSession('consultant')

// ─── Supabase mock — minimal chainable builder ────────────────────────────────
// Admin routes call createClient() for DB operations after the auth check.
// We return a mock that succeeds silently so we can reach the 200 branch.

function makeChain() {
  const chain: Record<string, unknown> = {}
  chain.select  = vi.fn(() => chain)
  chain.insert  = vi.fn(() => chain)
  chain.update  = vi.fn(() => chain)
  chain.delete  = vi.fn(() => chain)
  chain.eq      = vi.fn(() => chain)
  chain.in      = vi.fn(() => chain)
  chain.gte     = vi.fn(() => chain)
  chain.lte     = vi.fn(() => chain)
  chain.order   = vi.fn(() => chain)
  chain.range   = vi.fn(() => chain)
  chain.single  = vi.fn(() => Promise.resolve({ data: null, error: null }))
  chain.then    = (resolve: (v: unknown) => unknown) =>
    Promise.resolve({ data: [], error: null, count: 0 }).then(resolve)
  return chain
}

const mockSupabase = {
  from: vi.fn(() => makeChain()),
  auth: {
    getClaims: vi.fn(() => Promise.resolve({ data: { claims: { sub: 'auth-1' } }, error: null })),
  },
}

vi.mock('@/lib/supabase/server', () => ({
  createClient: vi.fn(() => Promise.resolve(mockSupabase)),
}))

// Admin client is used in POST /admin/users — return a stub that won't throw.
vi.mock('@/lib/supabase/admin', () => ({
  createAdminClient: vi.fn(() => ({
    auth: {
      admin: {
        createUser: vi.fn(() => Promise.resolve({ data: { user: { id: 'new-auth-1' } }, error: null })),
        deleteUser: vi.fn(() => Promise.resolve({ data: null, error: null })),
      },
    },
  })),
}))

// Notifications — fire-and-forget, stub to no-op.
vi.mock('@/lib/email/notifications', () => ({
  notifyInvitation: vi.fn(() => Promise.resolve()),
  notifyPasswordReset: vi.fn(() => Promise.resolve()),
}))

// Consultant sync — stub to no-op.
vi.mock('@/lib/auth/sync-user-consultant', () => ({
  syncProfileToConsultant: vi.fn(() => Promise.resolve()),
}))

// CSRF check — return null (no CSRF error) to let routes proceed.
vi.mock('@/lib/security', async (importOriginal) => {
  const original = await importOriginal<typeof import('@/lib/security')>()
  return { ...original, checkCsrf: vi.fn(() => null) }
})

// ─── getUserSession mock — controlled per test ────────────────────────────────

let currentSession: UserSession | null = null

vi.mock('@/lib/auth/permissions', async (importOriginal) => {
  const original = await importOriginal<typeof import('@/lib/auth/permissions')>()
  return {
    ...original,
    getUserSession: vi.fn(() => Promise.resolve(currentSession)),
  }
})

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

beforeEach(() => {
  currentSession = null
  vi.clearAllMocks()
  mockSupabase.from.mockImplementation(() => makeChain())
})

function makeRequest(method: string, url: string, body?: unknown): Request {
  return new Request(url, {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })
}

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

describe('POST /api/admin/users', () => {
  async function handler() {
    const { POST } = await import('@/app/api/admin/users/route')
    return POST
  }

  it('returns 401 when no session exists (unauthenticated)', async () => {
    currentSession = null
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users', {
        first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com', role_id: 'consultant',
      }) as Parameters<typeof POST>[0]
    )
    expect(res.status).toBe(401)
  })

  it('returns 403 when session role is consultant (non-admin)', async () => {
    currentSession = SESSION_CONSULTANT
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users', {
        first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com', role_id: 'consultant',
      }) as Parameters<typeof POST>[0]
    )
    expect(res.status).toBe(403)
  })

  it('proceeds past auth gate when session role is admin (admin can manage consultants)', async () => {
    currentSession = SESSION_ADMIN
    // The route will call createAdminClient and DB mocks — with the stubs above it
    // will attempt to look up a role and return a 400 (missing role row) rather than
    // 401/403. That is the correct behaviour: the auth gate was passed.
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users', {
        first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com', role_id: 'consultant',
      }) as Parameters<typeof POST>[0]
    )
    // Auth gate passed — status is NOT 401 or 403
    expect(res.status).not.toBe(401)
    expect(res.status).not.toBe(403)
  })
})

// ─── GET /api/admin/audit ─────────────────────────────────────────────────────

describe('GET /api/admin/audit', () => {
  async function handler() {
    const { GET } = await import('@/app/api/admin/audit/route')
    return GET
  }

  it('returns 401 when no session exists', async () => {
    currentSession = null
    const GET = await handler()
    const res = await GET(
      makeRequest('GET', 'http://localhost/api/admin/audit') as Parameters<typeof GET>[0]
    )
    expect(res.status).toBe(401)
  })

  it('returns 403 when role is consultant', async () => {
    currentSession = SESSION_CONSULTANT
    const GET = await handler()
    const res = await GET(
      makeRequest('GET', 'http://localhost/api/admin/audit') as Parameters<typeof GET>[0]
    )
    expect(res.status).toBe(403)
  })

  it('returns 200 when role is admin', async () => {
    currentSession = SESSION_ADMIN
    const GET = await handler()
    const res = await GET(
      makeRequest('GET', 'http://localhost/api/admin/audit') as Parameters<typeof GET>[0]
    )
    expect(res.status).toBe(200)
  })
})

// ─── POST /api/admin/users/lock ───────────────────────────────────────────────

describe('POST /api/admin/users/lock', () => {
  async function handler() {
    const { POST } = await import('@/app/api/admin/users/lock/route')
    return POST
  }

  const VALID_UUID = '00000000-0000-0000-0000-000000000001'

  it('returns 401 when no session exists', async () => {
    currentSession = null
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users/lock', { userId: VALID_UUID }) as Parameters<typeof POST>[0]
    )
    expect(res.status).toBe(401)
  })

  it('returns 403 when role is consultant', async () => {
    currentSession = SESSION_CONSULTANT
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users/lock', { userId: VALID_UUID }) as Parameters<typeof POST>[0]
    )
    expect(res.status).toBe(403)
  })

  it('passes auth gate when role is admin (status is not 401 or 403)', async () => {
    currentSession = SESSION_ADMIN
    const POST = await handler()
    const res = await POST(
      makeRequest('POST', 'http://localhost/api/admin/users/lock', { userId: VALID_UUID }) as Parameters<typeof POST>[0]
    )
    // Auth gate was passed — the route proceeded beyond the 401/403 check.
    // The DB stub may return a non-200 for other reasons; what matters is
    // the permission guard did not reject the request.
    expect(res.status).not.toBe(401)
    expect(res.status).not.toBe(403)
  })
})
