import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { ALL, GET, POST } from '../pages/api/collector/[...path]'

const originalFetch = globalThis.fetch
const originalConfigDir = process.env.OVERDECK_CONFIG_DIR
let configDir: string

beforeEach(() => {
  configDir = mkdtempSync(join(tmpdir(), 'overdeck-web-proxy-'))
  writeFileSync(join(configDir, 'token'), 'collector-token')
  process.env.OVERDECK_CONFIG_DIR = configDir
})

afterEach(() => {
  globalThis.fetch = originalFetch
  if (originalConfigDir === undefined) delete process.env.OVERDECK_CONFIG_DIR
  else process.env.OVERDECK_CONFIG_DIR = originalConfigDir
  rmSync(configDir, { recursive: true, force: true })
})

describe('collector mutation proxy', () => {
  it('accepts only JSON media-type essence and returns stable known-route methods', async () => {
    const url = new URL('http://deck.local/api/collector/config/hooks/background-jobs-blocker')
    const jsonp = new Request(url, { method: 'POST', body: JSON.stringify({ enabled: true }), headers: { origin: url.origin, 'content-type': 'application/jsonp' } })
    expect((await POST({ params: { path: 'config/hooks/background-jobs-blocker' }, request: jsonp } as never)).status).toBe(415)
    const method = await ALL({ params: { path: 'config/hooks' } } as never)
    expect(method.status).toBe(405)
    const postMethod = await POST({ params: { path: 'config/hooks' }, request: new Request(url, { method: 'POST' }) } as never)
    expect(postMethod.status).toBe(405)
    expect(await method.json()).toEqual({ error: 'method-not-allowed' })
  })
  it('allows only exact incident lifecycle mutation paths and forwards Retry-After', async () => {
    const targets: string[] = []
    globalThis.fetch = (async (input) => {
      targets.push(new URL(String(input)).pathname)
      return Response.json({ incident: { id: 'INC-1' } }, { status: 429, headers: { 'retry-after': '30' } })
    }) as typeof fetch
    const url = new URL('http://deck.local/api/collector/incidents')
    const request = (path: string) => new Request(new URL(`/api/collector/${path}`, url), {
      method: 'POST', body: '{}', headers: { origin: url.origin, 'content-type': 'application/json' },
    })

    for (const path of ['incidents', 'incidents/INC-1/dispatch', 'incidents/INC-1/stop', 'incidents/INC-1/delete']) {
      const response = await POST({ params: { path }, request: request(path) } as never)
      expect(response.status).toBe(429)
      expect(response.headers.get('retry-after')).toBe('30')
    }
    expect(targets).toEqual(['/incidents', '/incidents/INC-1/dispatch', '/incidents/INC-1/stop', '/incidents/INC-1/delete'])
  })

  it('proxies only exact factory run detail routes', async () => {
    let target: URL | undefined
    globalThis.fetch = (async (input) => {
      target = new URL(String(input))
      return Response.json({ adwId: 'run-1' })
    }) as typeof fetch
    const request = new Request('http://deck.local/api/collector/factory/runs/run-1')

    const allowed = await GET({ params: { path: 'factory/runs/run-1' }, url: new URL(request.url), request } as never)
    expect(allowed.status).toBe(200)
    expect(target?.pathname).toBe('/factory/runs/run-1')

    for (const path of ['factory/runs', 'factory/runs/run-1/events']) {
      const rejected = await GET({ params: { path }, url: new URL(request.url), request } as never)
      expect(rejected.status).toBe(404)
    }
  })

  it('forwards artifact ranges and size metadata', async () => {
    let headers: Headers | undefined
    globalThis.fetch = (async (_input, init) => {
      headers = new Headers(init?.headers)
      return new Response('chunk', {
        status: 206,
        headers: {
          'accept-ranges': 'bytes',
          'content-length': '5',
          'content-range': 'bytes 10-14/100',
          'content-type': 'text/plain; charset=utf-8',
          'x-overdeck-artifact-size': '100',
          'x-overdeck-artifact-tail': 'false',
        },
      })
    }) as typeof fetch
    const request = new Request('http://deck.local/api/collector/factory/artifact?path=logs/test.log', {
      headers: { range: 'bytes=10-14' },
    })

    const response = await GET({ params: { path: 'factory/artifact' }, url: new URL(request.url), request } as never)

    expect(response.status).toBe(206)
    expect(headers?.get('range')).toBe('bytes=10-14')
    expect(response.headers.get('accept-ranges')).toBe('bytes')
    expect(response.headers.get('content-range')).toBe('bytes 10-14/100')
    expect(response.headers.get('x-overdeck-artifact-size')).toBe('100')
    expect(response.headers.get('x-overdeck-artifact-tail')).toBe('false')
  })

  it('proxies only exact harness read routes and preserves cursor headers', async () => {
    let target: URL | undefined
    let headers: Headers | undefined
    globalThis.fetch = (async (input, init) => {
      target = new URL(String(input))
      headers = new Headers(init?.headers)
      return Response.json({ events: [] })
    }) as typeof fetch
    const request = new Request('http://deck.local/api/collector/harness/runs/run-1/tasks/task-1/stream?since=opaque')

    const allowed = await GET({ params: { path: 'harness/runs/run-1/tasks/task-1/stream' }, url: new URL(request.url), request } as never)
    expect(allowed.status).toBe(200)
    expect(target?.pathname).toBe('/harness/runs/run-1/tasks/task-1/stream')
    expect(target?.search).toBe('?since=opaque')
    expect(headers?.get('authorization')).toBe('Bearer collector-token')

    const rejected = await GET({ params: { path: 'harness/runs/run-1/arbitrary' }, url: new URL(request.url), request } as never)
    expect(rejected.status).toBe(404)
  })

  it('proxies the session screen route and nothing else under /sessions', async () => {
    let target: URL | undefined
    globalThis.fetch = (async (input) => {
      target = new URL(String(input))
      return Response.json({ ok: true, screen: '', capturedAt: '2026-08-07T00:00:00.000Z' })
    }) as typeof fetch
    const request = new Request('http://deck.local/api/collector/sessions/s1/screen')

    const allowed = await GET({ params: { path: 'sessions/s1/screen' }, url: new URL(request.url), request } as never)
    expect(allowed.status).toBe(200)
    expect(target?.pathname).toBe('/sessions/s1/screen')

    for (const path of ['sessions', 'sessions/s1', 'sessions/s1/attach']) {
      const rejected = await GET({ params: { path }, url: new URL(request.url), request } as never)
      expect(rejected.status).toBe(404)
    }
  })

  it('proxies /activity as a read path', async () => {
    let target: URL | undefined
    globalThis.fetch = (async (input) => {
      target = new URL(String(input))
      return Response.json({
        events: [],
        total: 0,
        truncated: false,
        limit: 0,
        categoryCounts: [],
        sourceCounts: [],
        coverage: [],
        suppressedNotifications: {
          status: 'ok',
          path: '/tmp',
          rows: [],
          sourceCount: 0,
          attemptCount: 0,
          skipped: 0,
        },
      })
    }) as typeof fetch

    const request = new Request('http://deck.local/api/collector/activity')
    const allowed = await GET({ params: { path: 'activity' }, url: new URL(request.url), request } as never)

    expect(allowed.status).toBe(200)
    expect(target?.pathname).toBe('/activity')
  })

  it('proxies only the exact cluster snapshot, node, and workload reads', async () => {
    const targets: string[] = []
    globalThis.fetch = (async (input) => {
      targets.push(new URL(String(input)).pathname)
      return Response.json({})
    }) as typeof fetch
    const base = new URL('http://deck.local/api/collector/cluster')
    const request = (path: string) => new Request(new URL(`/api/collector/${path}`, base))

    for (const path of ['cluster', 'cluster/nodes/builder-1', 'cluster/workloads/pod%2Fuid']) {
      expect((await GET({ params: { path }, url: new URL(request(path).url), request: request(path) } as never)).status).toBe(200)
    }
    for (const path of ['cluster/metrics', 'cluster/nodes', 'cluster/workloads', 'cluster/nodes/builder-1/delete']) {
      expect((await GET({ params: { path }, url: new URL(request(path).url), request: request(path) } as never)).status).toBe(404)
    }
    expect(targets).toEqual(['/cluster', '/cluster/nodes/builder-1', '/cluster/workloads/pod%2Fuid'])
  })

  it('accepts sessions.sendKeys only with same-origin proof', async () => {
    let called = false
    globalThis.fetch = (async () => {
      called = true
      return Response.json({ ok: true })
    }) as typeof fetch
    const url = new URL('http://deck.local/api/collector/actions/sessions.sendKeys')
    const body = JSON.stringify({ args: { id: 's1', key: 'Enter' } })

    const forged = await POST({
      params: { path: 'actions/sessions.sendKeys' },
      request: new Request(url, { method: 'POST', body, headers: { origin: 'http://evil.local' } }),
    } as never)
    expect(forged.status).toBe(403)
    expect(called).toBe(false)

    const allowed = await POST({
      params: { path: 'actions/sessions.sendKeys' },
      request: new Request(url, { method: 'POST', body, headers: { origin: url.origin } }),
    } as never)
    expect(allowed.status).toBe(200)
    expect(called).toBe(true)
  })

  it('requires same-origin CSRF proof and an explicit action verb', async () => {
    let called = false
    globalThis.fetch = (async () => {
      called = true
      return Response.json({ ok: true })
    }) as typeof fetch
    const url = new URL('http://deck.local/api/collector/actions/harness.run.kill')
    const body = JSON.stringify({ args: { runId: 'run', requestId: 'request' } })

    const missingOrigin = await POST({ params: { path: 'actions/harness.run.kill' }, request: new Request(url, { method: 'POST', body }) } as never)
    expect(missingOrigin.status).toBe(403)

    const forgedOrigin = await POST({
      params: { path: 'actions/harness.run.kill' },
      request: new Request(url, { method: 'POST', body, headers: { origin: 'http://evil.local' } }),
    } as never)
    expect(forgedOrigin.status).toBe(403)

    const allowed = await POST({
      params: { path: 'actions/harness.run.kill' },
      request: new Request(url, { method: 'POST', body, headers: { origin: url.origin } }),
    } as never)
    expect(allowed.status).toBe(200)
    expect(called).toBe(true)

    called = false
    const rejectedVerb = await POST({
      params: { path: 'actions/unknown' },
      request: new Request(url, { method: 'POST', body, headers: { origin: url.origin } }),
    } as never)
    expect(rejectedVerb.status).toBe(404)
    expect(called).toBe(false)

    const oversized = await POST({
      params: { path: 'actions/harness.run.kill' },
      request: new Request(url, { method: 'POST', body: 'x'.repeat(64 * 1024 + 1), headers: { origin: url.origin } }),
    } as never)
    expect(oversized.status).toBe(413)
  })
})
