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

/**
 * Imported participants (2026-09-14): a person from a historical order is
 * SHOWN on the trip but must not see it until they claim it (article §33).
 * These tests pin the two access gates (trip visibility, "my participant"
 * for the API guard) and the document URL resolver that lets a provider-
 * hosted file open without a bucket copy.
 */

// ---- in-memory stand-in for the two tables the gates read ------------------
type Row = Record<string, unknown>
const db: { participants: Row[]; documents: Row[] } = { participants: [], documents: [] }
const signed: string[] = []

function builder(table: 'participants' | 'documents') {
  const filters: Array<(r: Row) => boolean> = []
  let single = false
  const b: Record<string, unknown> = {
    select: () => b,
    order: () => b,
    limit: () => b,
    eq: (c: string, v: unknown) => (filters.push((r) => r[c] === v), b),
    neq: (c: string, v: unknown) => (filters.push((r) => r[c] !== v), b),
    in: (c: string, vs: unknown[]) => (filters.push((r) => vs.includes(r[c])), b),
    is: (c: string, v: unknown) => (filters.push((r) => r[c] === v), b),
    ilike: (c: string, pattern: string) => {
      const plain = pattern.replace(/\\([\\%_])/g, '$1').toLowerCase()
      filters.push((r) => String(r[c]).toLowerCase() === plain)
      return b
    },
    maybeSingle: () => ((single = true), b),
    then(resolve: (v: { data: unknown; error: null }) => void) {
      const hits = db[table].filter((r) => filters.every((f) => f(r)))
      resolve({ data: single ? (hits[0] ?? null) : hits, error: null })
    },
  }
  return b
}

vi.mock('@/lib/supabase/admin', () => ({
  createAdminClient: () => ({
    from: (t: string) => builder(t === 'trip_participants' ? 'participants' : 'documents'),
    storage: {
      from: () => ({
        createSignedUrl: async (path: string) => {
          signed.push(path)
          return { data: { signedUrl: `https://signed.example/${path}` } }
        },
      }),
    },
  }),
}))

const ME = { id: 'user-1', email: 'dana@carrier.example', name: 'Dana', role: 'dispatcher' as const, company: null, internal: false, originId: 'user-1' }
vi.mock('@/lib/auth/session', () => ({ readSession: async () => ({ sub: ME.id, email: ME.email, name: ME.name, role: ME.role, company: null, internal: false, iat: 1 }) }))
vi.mock('@/lib/auth/accounts', () => ({ effectiveRole: (env: { role: string }) => env.role, getAccountOverride: async () => null, isSessionRevoked: async () => false }))
vi.mock('@/lib/auth/env-users', () => ({ findEnvUserById: () => ({ id: ME.id, email: ME.email, name: ME.name, role: ME.role, company: null, internal: false }) }))
vi.mock('next/navigation', () => ({ redirect: () => {} }))

const { ACCESS_STATUSES, grantsAccess } = await import('@/lib/domain/participants')
const { getVisibleTripIds, canAccessTrip } = await import('@/lib/data/trips')
const { getMyParticipant } = await import('@/lib/auth')
const { resolveDocumentUrl, resolveDocumentUrls } = await import('@/lib/data/document-urls')

beforeEach(() => {
  db.participants = []
  db.documents = []
  signed.length = 0
})

describe('participant statuses that grant access', () => {
  it('invited and active do; imported and removed do not', () => {
    expect([...ACCESS_STATUSES]).toEqual(['invited', 'active'])
    expect(grantsAccess('active')).toBe(true)
    expect(grantsAccess('invited')).toBe(true)
    expect(grantsAccess('imported')).toBe(false)
    expect(grantsAccess('removed')).toBe(false)
    expect(grantsAccess(null)).toBe(false)
  })
})

describe('trip visibility', () => {
  it('an imported participant row shows no trip, by id or by email', async () => {
    db.participants = [
      { trip_id: 'trip-hist', user_id: null, email: 'dana@carrier.example', role: 'dispatcher', status: 'imported' },
      { trip_id: 'trip-hist-2', user_id: 'user-1', email: 'dana@carrier.example', role: 'dispatcher', status: 'imported' },
    ]
    expect(await getVisibleTripIds(ME)).toEqual([])
    expect(await canAccessTrip(ME, 'trip-hist')).toBe(false)
    expect(await canAccessTrip(ME, 'trip-hist-2')).toBe(false)
  })
  it('the same row, once claimed (active), shows the trip', async () => {
    db.participants = [{ trip_id: 'trip-hist', user_id: 'user-1', email: 'dana@carrier.example', role: 'dispatcher', status: 'active' }]
    expect(await getVisibleTripIds(ME)).toEqual(['trip-hist'])
    expect(await canAccessTrip(ME, 'trip-hist')).toBe(true)
  })
  it('a live invitation still counts, a removed row still does not', async () => {
    db.participants = [
      { trip_id: 'trip-a', user_id: null, email: 'DANA@carrier.example', role: 'driver', status: 'invited' },
      { trip_id: 'trip-b', user_id: 'user-1', email: 'dana@carrier.example', role: 'driver', status: 'removed' },
    ]
    expect(await getVisibleTripIds(ME)).toEqual(['trip-a'])
  })
  it('admins see everything regardless', async () => {
    db.participants = [{ trip_id: 'trip-hist', user_id: null, email: 'someone@else.example', role: 'driver', status: 'imported' }]
    expect(await canAccessTrip({ ...ME, role: 'admin' }, 'trip-hist')).toBe(true)
  })
})

describe('the API guard ("my participant")', () => {
  it('does not treat an imported row as my participation', async () => {
    db.participants = [{ trip_id: 'trip-hist', user_id: null, email: 'dana@carrier.example', role: 'dispatcher', status: 'imported' }]
    expect(await getMyParticipant('trip-hist')).toBeNull()
  })
  it('does once it is active', async () => {
    db.participants = [{ trip_id: 'trip-hist', user_id: null, email: 'dana@carrier.example', role: 'dispatcher', status: 'active' }]
    expect((await getMyParticipant('trip-hist'))?.role).toBe('dispatcher')
  })
})

describe('document URLs', () => {
  it('a provider-hosted file is passed through, a bucket file is signed', async () => {
    expect(await resolveDocumentUrl({ id: 'd1', storage_path: null, storage_provider: 'external', external_url: 'https://provider.example/permit.pdf' })).toBe('https://provider.example/permit.pdf')
    expect(await resolveDocumentUrl({ id: 'd2', storage_path: 'trip/x.pdf' })).toBe('https://signed.example/trip/x.pdf')
    expect(signed).toEqual(['trip/x.pdf'])
  })
  it('a row with neither location yields nothing rather than a broken link', async () => {
    expect(await resolveDocumentUrl({ id: 'd3', storage_path: null, external_url: null })).toBeNull()
    const urls = await resolveDocumentUrls([
      { id: 'a', storage_path: 'p/a.pdf' },
      { id: 'b', storage_path: null, storage_provider: 'external', external_url: 'https://provider.example/b.pdf' },
      { id: 'c', storage_path: null, external_url: null },
    ])
    expect(urls).toEqual({ a: 'https://signed.example/p/a.pdf', b: 'https://provider.example/b.pdf' })
  })
})
