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

import type { CallerIdentity } from '../src/auth.js'
import { AdmissionError, RequestAdmission, type AdmissionKey, type IdempotencyReservation, type IdempotencyStore } from '../src/admission.js'

const now = new Date('2026-08-13T12:00:00.000Z')
const identity: CallerIdentity = {
  subject: 'owner-1',
  email: 'owner@example.test',
  edge_session_id: 'session-1',
  application_credential_id: 'credential-1',
}
const policy = {
  maxRequestBytes: 2_048,
  maxAgeMilliseconds: 60_000,
  rate: { maxRequests: 3, windowMilliseconds: 60_000 },
  maxConcurrent: 1,
}

function request(overrides: Record<string, unknown> = {}) {
  return {
    request_id: 'request-1',
    idempotency_key: 'key-1',
    issued_at: now.toISOString(),
    nonce: 'nonce-1',
    operation: 'deploy.read',
    payload: { project: 'overdeck', branch: 'main' },
    ...overrides,
  }
}

function wireBody(input: unknown): Uint8Array {
  return new TextEncoder().encode(JSON.stringify(input))
}

function admit<Outcome>(
  admission: RequestAdmission<Outcome>,
  input: ReturnType<typeof request>,
  inputPolicy = policy,
) {
  return admission.admit(identity, wireBody(input), inputPolicy)
}

class MemoryIdempotencyStore<Outcome> implements IdempotencyStore<Outcome> {
  private readonly reservations = new Map<string, IdempotencyReservation>()
  private readonly outcomes = new Map<string, Outcome>()

  async reserve(reservation: IdempotencyReservation) {
    const key = this.key(reservation.key, reservation.idempotencyKey)
    const existing = this.reservations.get(key)
    if (!existing) {
      this.reservations.set(key, reservation)
      return { kind: 'reserved' as const }
    }
    if (existing.requestHash !== reservation.requestHash) {
      return { kind: 'conflict' as const }
    }
    const outcome = this.outcomes.get(key)
    if (outcome === undefined) {
      return { kind: 'conflict' as const }
    }
    return { kind: 'replay' as const, outcome }
  }

  async complete(reservation: IdempotencyReservation, outcome: Outcome) {
    this.outcomes.set(this.key(reservation.key, reservation.idempotencyKey), outcome)
  }

  async abandon(reservation: IdempotencyReservation) {
    const key = this.key(reservation.key, reservation.idempotencyKey)
    if (!this.outcomes.has(key)) {
      this.reservations.delete(key)
    }
  }

  private key(key: Pick<AdmissionKey, 'subject'>, idempotencyKey: string): string {
    return `${key.subject}:${idempotencyKey}`
  }
}

function dependencies<Outcome = string>(overrides: Record<string, unknown> = {}) {
  const usedNonces = new Set<string>()
  return {
    nonceStore: {
      async consume(key: Pick<AdmissionKey, 'subject'>, nonce: string) {
        const value = `${key.subject}:${nonce}`
        if (usedNonces.has(value)) return false
        usedNonces.add(value)
        return true
      },
    },
    idempotencyStore: new MemoryIdempotencyStore<Outcome>(),
    rateLimiter: { acquire: async () => true },
    concurrencyLimiter: { acquire: async () => async () => {} },
    now: () => now,
    ...overrides,
  }
}

async function expectAdmissionCode(promise: Promise<unknown>, code: string): Promise<void> {
  await expect(promise).rejects.toMatchObject({ code, message: 'Request admission failed.' })
}

describe('RequestAdmission', () => {
  it('binds idempotency reuse to canonical full request identity', async () => {
    const stores = dependencies()
    const admission = new RequestAdmission<string>(stores)
    const accepted = await admit(admission, request())
    expect(accepted.kind).toBe('accepted')
    if (accepted.kind === 'accepted') await accepted.complete('operation-123')

    const retry = await admit(admission, request({ payload: { branch: 'main', project: 'overdeck' } }))
    expect(retry).toEqual({ kind: 'replay', outcome: 'operation-123' })

    for (const changedRequest of [
      request({ request_id: 'request-2' }),
      request({ issued_at: '2026-08-13T12:00:01.000Z' }),
      request({ nonce: 'nonce-2' }),
      request({ operation: 'deploy.write' }),
      request({ payload: { project: 'other' } }),
    ]) {
      await expectAdmissionCode(admit(admission, changedRequest), 'idempotency_conflict')
    }
  })

  it('rejects replay before dispatch', async () => {
    const admission = new RequestAdmission<string>(dependencies())
    const first = await admit(admission, request())
    if (first.kind === 'accepted') await first.abandon()
    await expectAdmissionCode(admit(admission, request()), 'replay_detected')
  })

  it('rejects nonce reuse under another operation before expiry', async () => {
    const admission = new RequestAdmission<string>(dependencies())
    const first = await admit(admission, request())
    if (first.kind === 'accepted') await first.abandon()

    await expectAdmissionCode(admit(admission, request({ operation: 'deploy.write', idempotency_key: 'key-2' })), 'replay_detected')
  })

  it.each([
    ['expired request', request({ issued_at: '2026-08-13T11:58:59.999Z' }), policy, 'request_expired'],
    ['future request', request({ issued_at: '2026-08-13T12:00:30.001Z' }), policy, 'request_expired'],
    ['oversized request', request({ payload: 'x'.repeat(3_000) }), policy, 'invalid_request'],
    ['deep request', request({ payload: deeplyNestedPayload(200) }), policy, 'invalid_request'],
    ['invalid date', request({ issued_at: 'not-a-date' }), policy, 'invalid_request'],
    ['unknown request field', request({ unknown: 'value' }), policy, 'invalid_request'],
  ])('rejects %s', async (_name, input, inputPolicy, code) => {
    const admission = new RequestAdmission<string>(dependencies())
    await expectAdmissionCode(admit(admission, input, inputPolicy), code)
  })

  it('rejects parsed lookalike objects at the admission boundary', async () => {
    const admission = new RequestAdmission<string>(dependencies())

    await expectAdmissionCode(
      admission.admit(identity, request() as unknown as Uint8Array, policy),
      'invalid_request',
    )
  })

  it('rejects whitespace-padded wire body before request processing', async () => {
    const input = request()
    const paddedWireBody = new TextEncoder().encode(`${JSON.stringify(input)}${' '.repeat(policy.maxRequestBytes)}`)
    const admission = new RequestAdmission<string>(dependencies())

    await expectAdmissionCode(
      admission.admit(identity, paddedWireBody, policy),
      'invalid_request',
    )
    expect(wireBody(input).byteLength).toBeLessThan(policy.maxRequestBytes)
  })

  it('enforces rate and concurrency limits after durable reservation', async () => {
    const rateLimited = new RequestAdmission<string>(dependencies({ rateLimiter: { acquire: async () => false } }))
    await expectAdmissionCode(admit(rateLimited, request()), 'rate_limited')

    const capacityLimited = new RequestAdmission<string>(dependencies({ concurrencyLimiter: { acquire: async () => undefined } }))
    await expectAdmissionCode(admit(capacityLimited, request()), 'capacity_exhausted')
  })

  it('maps backend errors to opaque admission errors', async () => {
    const admission = new RequestAdmission<string>(dependencies({
      idempotencyStore: {
        reserve: async () => { throw new Error('sqlite /srv/private/state.db failed') },
        complete: async () => {},
        abandon: async () => {},
      },
    }))
    await expectAdmissionCode(admit(admission, request()), 'service_unavailable')
  })

  it('retries equivalent object completion after durable-store failure and rejects conflicting settlement', async () => {
    let completeCalls = 0
    let releases = 0
    const admission = new RequestAdmission<{ operation: string; result: { id: string } }>(dependencies<{ operation: string; result: { id: string } }>({
      idempotencyStore: {
        reserve: async () => ({ kind: 'reserved' as const }),
        complete: async () => {
          completeCalls += 1
          if (completeCalls === 1) throw new Error('durable store unavailable')
        },
        abandon: async () => {},
      },
      concurrencyLimiter: { acquire: async () => async () => { releases += 1 } },
    }))
    const result = await admit(admission, request())
    if (result.kind !== 'accepted') throw new Error('Expected accepted admission.')

    await expectAdmissionCode(result.complete({ operation: 'operation-123', result: { id: 'result-456' } }), 'service_unavailable')
    await expect(result.complete({ result: { id: 'result-456' }, operation: 'operation-123' })).resolves.toBeUndefined()
    await expectAdmissionCode(result.abandon(), 'invalid_request')
    expect({ completeCalls, releases }).toEqual({ completeCalls: 2, releases: 1 })
  })

  it('retries same abandonment after release failure without repeating durable write', async () => {
    let abandonCalls = 0
    let releases = 0
    const admission = new RequestAdmission<string>(dependencies({
      idempotencyStore: {
        reserve: async () => ({ kind: 'reserved' as const }),
        complete: async () => {},
        abandon: async () => { abandonCalls += 1 },
      },
      concurrencyLimiter: {
        acquire: async () => async () => {
          releases += 1
          if (releases === 1) throw new Error('release unavailable')
        },
      },
    }))
    const result = await admit(admission, request())
    if (result.kind !== 'accepted') throw new Error('Expected accepted admission.')

    await expectAdmissionCode(result.abandon(), 'service_unavailable')
    await expect(result.abandon()).resolves.toBeUndefined()
    expect({ abandonCalls, releases }).toEqual({ abandonCalls: 1, releases: 2 })
  })

  it('shares concurrent same-action settlement and rejects conflicting action', async () => {
    let resolveComplete: (() => void) | undefined
    let completeCalls = 0
    let abandonCalls = 0
    let releases = 0
    const completeGate = new Promise<void>((resolve) => { resolveComplete = resolve })
    const admission = new RequestAdmission<string>(dependencies({
      idempotencyStore: {
        reserve: async () => ({ kind: 'reserved' as const }),
        complete: async () => {
          completeCalls += 1
          await completeGate
        },
        abandon: async () => { abandonCalls += 1 },
      },
      concurrencyLimiter: { acquire: async () => async () => { releases += 1 } },
    }))
    const result = await admit(admission, request())
    if (result.kind !== 'accepted') throw new Error('Expected accepted admission.')

    const firstCompletion = result.complete('operation-123')
    const secondCompletion = result.complete('operation-123')
    const abandonment = result.abandon()
    resolveComplete!()

    await expect(firstCompletion).resolves.toBeUndefined()
    await expect(secondCompletion).resolves.toBeUndefined()
    await expectAdmissionCode(abandonment, 'invalid_request')
    expect({ completeCalls, abandonCalls, releases }).toEqual({ completeCalls: 1, abandonCalls: 0, releases: 1 })
  })

  it('does not expose internal admission details in errors', () => {
    expect(new AdmissionError('rate_limited').message).toBe('Request admission failed.')
  })
})

function deeplyNestedPayload(depth: number): unknown {
  let value: unknown = 'value'
  for (let index = 0; index < depth; index += 1) {
    value = { value }
  }
  return value
}
