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

import {
  GatewayError,
  parseOperationRequest,
  type CallerIdentity,
} from '../src/contracts.js'
import { createRouteRegistry } from '../src/routes.js'

function rawRequest(value: unknown): Uint8Array {
  return new TextEncoder().encode(JSON.stringify(value))
}

function parseRequest(value: unknown) {
  return parseOperationRequest(rawRequest(value), 1024)
}

const caller: CallerIdentity = {
  subject: 'owner-123',
  email: 'owner@example.com',
  edge_session_id: 'edge-session-123',
  application_credential_id: 'credential-123',
}

describe('Actions Gateway contracts', () => {
  it('parses a complete operation request without changing its payload', () => {
    const wireBody = rawRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: { project_id: 'project-123' },
    })
    const request = parseOperationRequest(wireBody, 1024)

    expect(request).toEqual({
      requestId: 'req-123',
      idempotencyKey: 'idem-123',
      issuedAt: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: { project_id: 'project-123' },
    })
  })

  it('rejects oversized raw bytes before parsing', () => {
    const parse = vi.spyOn(JSON, 'parse')

    expect(() => parseOperationRequest(new Uint8Array(129), 128)).toThrowError(
      expect.objectContaining({ code: 'invalid_request' }),
    )
    expect(parse).not.toHaveBeenCalled()
  })

  it('rejects non-Uint8Array, detached, and spoofed raw bodies before parsing', () => {
    const parse = vi.spyOn(JSON, 'parse')
    const oversized = new Uint8Array(129)
    Object.defineProperty(oversized, 'byteLength', { value: 0 })
    const detachedSource = rawRequest({})
    const detached = structuredClone(detachedSource, { transfer: [detachedSource.buffer] })

    for (const wireBody of [
      {},
      new Int8Array(0),
      new DataView(new ArrayBuffer(0)),
      oversized,
      detachedSource,
    ]) {
      expect(() => parseOperationRequest(wireBody as never, 128)).toThrowError(
        expect.objectContaining({ code: 'invalid_request' }),
      )
    }
    expect(parse).not.toHaveBeenCalled()
  })

  it('uses intrinsic byte length despite a spoofed own property', () => {
    const wireBody = rawRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: {},
    })
    Object.defineProperty(wireBody, 'byteLength', { value: 0 })

    expect(() => parseOperationRequest(wireBody, wireBody.buffer.byteLength - 1)).toThrowError(
      expect.objectContaining({ code: 'invalid_request' }),
    )
  })
  it('rejects invalid size limits before parsing', () => {
    for (const limit of [-1, 1.5, Number.NaN]) {
      expect(() => parseOperationRequest(rawRequest({}), limit)).toThrowError(
        expect.objectContaining({ code: 'invalid_request' }),
      )
    }
  })

  it('rejects direct dispatch of caller-constructed parsed requests', async () => {
    const dispatch = vi.fn(async () => ({ operationId: 'op-123' }))
    const registry = createRouteRegistry([{
      operation: 'project.status.get',
      service: 'project',
      authorityClass: 'read',
      requestLimitBytes: 1024,
      ratePolicy: { limit: 10, windowMs: 60_000 },
      approvalRequired: false,
      parseRequest: () => ({}),
      parseResponse: (value: unknown) => value,
      adapter: {
        dispatch,
        async getStatus() {
          return { state: 'completed' }
        },
      },
    }])

    await expect(registry.dispatch(caller, {
      requestId: 'req-123',
      idempotencyKey: 'idem-123',
      issuedAt: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: {},
    } as never)).rejects.toMatchObject({ code: 'invalid_request' })
    expect(dispatch).not.toHaveBeenCalled()
  })

  it('rejects unknown request fields with a stable invalid-request error', () => {
    expect(() => parseRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: {},
      debug: true,
    })).toThrowError(expect.objectContaining({ code: 'invalid_request' }))
  })

  it('binds a route to its schema and only dispatches validated payloads', async () => {
    const registry = createRouteRegistry([
      {
        operation: 'project.status.get',
        service: 'project',
        authorityClass: 'read',
        requestLimitBytes: 1024,
        ratePolicy: { limit: 10, windowMs: 60_000 },
        approvalRequired: false,
        parseRequest(payload) {
          if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
            throw new GatewayError('invalid_request')
          }
          const record = payload as Record<string, unknown>
          if (Object.keys(record).length !== 1 || typeof record.project_id !== 'string') {
            throw new GatewayError('invalid_request')
          }
          return { projectId: record.project_id }
        },
        parseResponse(value) {
          if (typeof value !== 'object' || value === null || Array.isArray(value)) {
            throw new GatewayError('service_unavailable')
          }
          const record = value as Record<string, unknown>
          if (Object.keys(record).length !== 1 || typeof record.state !== 'string') {
            throw new GatewayError('service_unavailable')
          }
          return { state: record.state }
        },
        adapter: {
          async dispatch(identity, request) {
            expect(identity).toEqual(caller)
            expect(request).toEqual({ projectId: 'project-123' })
            return { operationId: 'op-123' }
          },
          async getStatus() {
            return { state: 'completed' }
          },
        },
      },
    ])

    const handle = await registry.dispatch(caller, rawRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: { project_id: 'project-123' },
    }))

    expect(handle).toEqual({ operationId: 'op-123' })
  })

  it('returns only validated opaque status data', async () => {
    const registry = createRouteRegistry([
      {
        operation: 'project.status.get',
        service: 'project',
        authorityClass: 'read',
        requestLimitBytes: 1024,
        ratePolicy: { limit: 10, windowMs: 60_000 },
        approvalRequired: false,
        parseRequest: () => ({}),
        parseResponse(value) {
          if (typeof value !== 'object' || value === null || Array.isArray(value)) {
            throw new GatewayError('service_unavailable')
          }
          const record = value as Record<string, unknown>
          if (Object.keys(record).length !== 1 || typeof record.state !== 'string') {
            throw new GatewayError('service_unavailable')
          }
          return { state: record.state }
        },
        adapter: {
          async dispatch() {
            return { operationId: 'op-123' }
          },
          async getStatus() {
            return { state: 'completed', host_path: '/private/host/path' }
          },
        },
      },
    ])

    await expect(registry.getStatus(caller, 'project.status.get', 'op-123'))
      .rejects.toMatchObject({ code: 'service_unavailable' })
  })

  it('rejects invalid adapter operation handles without leaking their values', async () => {
    const registry = createRouteRegistry([
      {
        operation: 'project.status.get',
        service: 'project',
        authorityClass: 'read',
        requestLimitBytes: 1024,
        ratePolicy: { limit: 10, windowMs: 60_000 },
        approvalRequired: false,
        parseRequest: () => ({}),
        parseResponse: (value: unknown) => value,
        adapter: {
          async dispatch() {
            return { operationId: '/private/host/path' }
          },
          async getStatus() {
            return { state: 'completed' }
          },
        },
      },
    ])

    await expect(registry.dispatch(caller, rawRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: {},
    }))).rejects.toMatchObject({ code: 'service_unavailable' })
  })

  it('rejects malformed route policy and adapter contracts at construction', () => {
    const route = {
      operation: 'project.status.get',
      service: 'project',
      authorityClass: 'read-write',
      requestLimitBytes: 1024,
      ratePolicy: { limit: 10, windowMs: 60_000 },
      approvalRequired: 'false',
      parseRequest: undefined,
      parseResponse: undefined,
      adapter: {},
    }

    expect(() => createRouteRegistry([route as unknown as never])).toThrowError(
      expect.objectContaining({ code: 'invalid_request' }),
    )
  })

  it('rejects deeply nested payloads through the bounded route-size gate', async () => {
    const payload = { child: 'x'.repeat(2_000) }
    const registry = createRouteRegistry([
      {
        operation: 'project.status.get',
        service: 'project',
        authorityClass: 'read',
        requestLimitBytes: 1024,
        ratePolicy: { limit: 10, windowMs: 60_000 },
        approvalRequired: false,
        parseRequest: () => ({}),
        parseResponse: (value: unknown) => value,
        adapter: {
          async dispatch() {
            throw new Error('must not dispatch')
          },
          async getStatus() {
            return { state: 'completed' }
          },
        },
      },
    ])

    await expect(registry.dispatch(caller, rawRequest({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload,
    }))).rejects.toMatchObject({ code: 'invalid_request' })
  })

  it('rejects a request that exceeds its route limit through envelope bytes and whitespace before dispatch', async () => {
    const dispatch = vi.fn(async () => ({ operationId: 'op-123' }))
    const registry = createRouteRegistry([
      {
        operation: 'project.status.get',
        service: 'project',
        authorityClass: 'read',
        requestLimitBytes: 128,
        ratePolicy: { limit: 10, windowMs: 60_000 },
        approvalRequired: false,
        parseRequest: () => ({}),
        parseResponse: (value: unknown) => value,
        adapter: {
          dispatch,
          async getStatus() {
            return { state: 'completed' }
          },
        },
      },
    ])
    const wireBody = JSON.stringify({
      request_id: 'req-123',
      idempotency_key: 'idem-123',
      issued_at: '2026-08-13T10:00:00.000Z',
      nonce: 'nonce-123',
      operation: 'project.status.get',
      payload: {},
    }, null, 2)

    await expect(registry.dispatch(caller, new TextEncoder().encode(wireBody))).rejects.toMatchObject({
      code: 'invalid_request',
    })
    expect(dispatch).not.toHaveBeenCalled()
  })

  it('rejects duplicate operations at registry construction', () => {
    const route = {
      operation: 'project.status.get',
      service: 'project',
      authorityClass: 'read' as const,
      requestLimitBytes: 1024,
      ratePolicy: { limit: 10, windowMs: 60_000 },
      approvalRequired: false,
      parseRequest: () => ({}),
      parseResponse: (value: unknown) => value,
      adapter: {
        async dispatch() {
          return { operationId: 'op-123' }
        },
        async getStatus() {
          return { state: 'completed' }
        },
      },
    }

    expect(() => createRouteRegistry([route, route])).toThrowError(
      expect.objectContaining({ code: 'invalid_request' }),
    )
  })
})
