/**
 * Access Control Test Utilities
 *
 * Helper functions for testing access control logic in Payload CMS collections.
 * Provides mock request builders, user factories, and tenant resolution mocks.
 */

import type { PayloadRequest, Access, Where } from 'payload'
import { vi } from 'vitest'

// ============================================================================
// Types
// ============================================================================

export interface MockUser {
  id: number
  email: string
  isSuperAdmin?: boolean
  tenants?: Array<{ tenant: { id: number } | number }>
}

export interface MockTenant {
  id: number
  name: string
  slug: string
  domains?: Array<{ domain: string }>
}

// Note: Not extending PayloadRequest to allow flexible mock types for testing
export interface MockPayloadRequest {
  user?: MockUser | null
  // Allow both Headers and plain object for testing different header formats
  headers: Headers | Record<string, string | string[] | undefined>
  payload: {
    find: ReturnType<typeof vi.fn>
    findByID: ReturnType<typeof vi.fn>
    create: ReturnType<typeof vi.fn>
    update: ReturnType<typeof vi.fn>
    delete: ReturnType<typeof vi.fn>
  }
}

// ============================================================================
// User Factory
// ============================================================================

/**
 * Create a mock super admin user
 */
export function createSuperAdmin(overrides: Partial<MockUser> = {}): MockUser {
  return {
    id: 1,
    email: 'superadmin@example.com',
    isSuperAdmin: true,
    tenants: [],
    ...overrides,
  }
}

/**
 * Create a mock regular user with tenant assignment
 */
export function createTenantUser(
  tenantIds: number[],
  overrides: Partial<MockUser> = {},
): MockUser {
  return {
    id: 2,
    email: 'user@example.com',
    isSuperAdmin: false,
    tenants: tenantIds.map((id) => ({ tenant: { id } })),
    ...overrides,
  }
}

/**
 * Create a mock user with tenant as primitive (alternative format)
 */
export function createTenantUserPrimitive(
  tenantIds: number[],
  overrides: Partial<MockUser> = {},
): MockUser {
  return {
    id: 3,
    email: 'user-primitive@example.com',
    isSuperAdmin: false,
    tenants: tenantIds.map((id) => ({ tenant: id })),
    ...overrides,
  }
}

/**
 * Create an anonymous user (null)
 */
export function createAnonymousUser(): null {
  return null
}

// ============================================================================
// Tenant Factory
// ============================================================================

/**
 * Create a mock tenant
 */
export function createMockTenant(overrides: Partial<MockTenant> = {}): MockTenant {
  const id = overrides.id ?? 1
  return {
    id,
    name: `Tenant ${id}`,
    slug: `tenant-${id}`,
    domains: [{ domain: `tenant${id}.example.com` }],
    ...overrides,
  }
}

// ============================================================================
// Request Factory
// ============================================================================

/**
 * Create a mock PayloadRequest with user
 */
export function createMockPayloadRequest(
  user: MockUser | null,
  options: {
    host?: string
    tenants?: MockTenant[]
  } = {},
): MockPayloadRequest {
  const headers = new Headers()

  if (options.host) {
    headers.set('host', options.host)
  }

  // Mock payload.find to resolve tenant from host
  const mockFind = vi.fn().mockImplementation(async (args: { collection: string; where?: Where }) => {
    if (args.collection === 'tenants' && options.tenants) {
      // Extract domain from where clause
      const domainQuery = args.where?.['domains.domain'] as { equals?: string } | undefined
      const domain = domainQuery?.equals

      if (domain) {
        const tenant = options.tenants.find((t) =>
          t.domains?.some((d) => d.domain === domain),
        )
        return {
          docs: tenant ? [tenant] : [],
          totalDocs: tenant ? 1 : 0,
          page: 1,
          totalPages: tenant ? 1 : 0,
        }
      }
    }
    return { docs: [], totalDocs: 0, page: 1, totalPages: 0 }
  })

  return {
    user,
    headers,
    payload: {
      find: mockFind,
      findByID: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    },
  } as MockPayloadRequest
}

/**
 * Create a mock request for authenticated user
 */
export function createAuthenticatedRequest(
  user: MockUser,
  host?: string,
): MockPayloadRequest {
  return createMockPayloadRequest(user, { host })
}

/**
 * Create a mock request for anonymous user with host
 */
export function createAnonymousRequest(
  host: string,
  tenants: MockTenant[] = [],
): MockPayloadRequest {
  return createMockPayloadRequest(null, { host, tenants })
}

// ============================================================================
// Access Control Result Helpers
// ============================================================================

export type AccessResult = boolean | Where

/**
 * Check if access result is a boolean true (full access)
 */
export function hasFullAccess(result: AccessResult): boolean {
  return result === true
}

/**
 * Check if access result is a boolean false (no access)
 */
export function hasNoAccess(result: AccessResult): boolean {
  return result === false
}

/**
 * Check if access result is a Where constraint (filtered access)
 */
export function hasFilteredAccess(result: AccessResult): result is Where {
  return typeof result === 'object' && result !== null
}

/**
 * Extract tenant ID from filtered access result
 */
export function getTenantIdFromFilter(result: Where): number | null {
  const tenantFilter = result.tenant as { equals?: number } | undefined
  if (tenantFilter?.equals !== undefined) {
    return tenantFilter.equals
  }
  return null
}

/**
 * Extract tenant IDs from "in" filter
 */
export function getTenantIdsFromInFilter(result: Where): number[] {
  const tenantFilter = result.tenant as { in?: number[] } | undefined
  if (tenantFilter?.in) {
    return tenantFilter.in
  }
  return []
}

// ============================================================================
// Assertion Helpers
// ============================================================================

/**
 * Assert that access is granted (true)
 */
export function assertAccessGranted(result: AccessResult): void {
  if (result !== true) {
    throw new Error(`Expected full access (true), got: ${JSON.stringify(result)}`)
  }
}

/**
 * Assert that access is denied (false)
 */
export function assertAccessDenied(result: AccessResult): void {
  if (result !== false) {
    throw new Error(`Expected access denied (false), got: ${JSON.stringify(result)}`)
  }
}

/**
 * Assert that access is filtered by tenant
 */
export function assertTenantFiltered(result: AccessResult, expectedTenantId: number): void {
  if (!hasFilteredAccess(result)) {
    throw new Error(`Expected tenant filter, got: ${JSON.stringify(result)}`)
  }

  const tenantId = getTenantIdFromFilter(result)
  if (tenantId !== expectedTenantId) {
    throw new Error(`Expected tenant ID ${expectedTenantId}, got: ${tenantId}`)
  }
}

/**
 * Assert that access is filtered by multiple tenants (IN clause)
 */
export function assertTenantsFiltered(result: AccessResult, expectedTenantIds: number[]): void {
  if (!hasFilteredAccess(result)) {
    throw new Error(`Expected tenant filter, got: ${JSON.stringify(result)}`)
  }

  const tenantIds = getTenantIdsFromInFilter(result)
  const sortedExpected = [...expectedTenantIds].sort()
  const sortedActual = [...tenantIds].sort()

  if (JSON.stringify(sortedExpected) !== JSON.stringify(sortedActual)) {
    throw new Error(
      `Expected tenant IDs [${sortedExpected.join(', ')}], got: [${sortedActual.join(', ')}]`,
    )
  }
}

// ============================================================================
// Access Function Wrapper
// ============================================================================

/**
 * Execute an access function with mock context
 */
export async function executeAccess(
  accessFn: Access,
  request: MockPayloadRequest,
  options: {
    id?: string | number
    data?: Record<string, unknown>
  } = {},
): Promise<AccessResult> {
  // Convert string ID to number if needed (Payload access functions expect number | undefined)
  const numericId = typeof options.id === 'string' ? parseInt(options.id, 10) : options.id
  const result = await accessFn({
    req: request as unknown as PayloadRequest,
    id: numericId,
    data: options.data,
  })

  return result
}

// ============================================================================
// Test Data
// ============================================================================

export const TEST_TENANTS = {
  porwoll: createMockTenant({
    id: 1,
    name: 'porwoll.de',
    slug: 'porwoll',
    domains: [{ domain: 'porwoll.de' }],
  }),
  c2s: createMockTenant({
    id: 4,
    name: 'Complex Care Solutions GmbH',
    slug: 'c2s',
    domains: [{ domain: 'complexcaresolutions.de' }],
  }),
  gunshin: createMockTenant({
    id: 5,
    name: 'Gunshin',
    slug: 'gunshin',
    domains: [{ domain: 'gunshin.de' }],
  }),
}

export const TEST_USERS = {
  superAdmin: createSuperAdmin({ id: 1, email: 'admin@c2sgmbh.de' }),
  porwollUser: createTenantUser([1], { id: 2, email: 'user@porwoll.de' }),
  c2sUser: createTenantUser([4], { id: 3, email: 'user@c2s.de' }),
  multiTenantUser: createTenantUser([1, 4, 5], { id: 4, email: 'multi@example.com' }),
}
