import { mkdtempSync, rmSync } from 'node:fs'
import { type Server, createServer } from 'node:http'
import type { AddressInfo } from 'node:net'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { Hono } from 'hono'
import { afterAll, describe, expect, it } from 'vitest'

const tmp = mkdtempSync(join(tmpdir(), 'thomas-mcprelay-'))
process.env.THOMAS_HOME = tmp

const { handleMcpRelay } = await import('./relay.ts')
const { initRoutesTable, stopWatcher } = await import('../routes/load.ts')
const { upsertRoutes } = await import('../../cli/wire/routes.ts')
const { getRawDb, closeDb } = await import('../store/db.ts')

// Fake MCP upstream: POST → JSON-RPC result; GET → 2-event SSE stream.
const upstream: Server = createServer((req, res) => {
  if (req.method === 'POST') {
    res.writeHead(200, { 'content-type': 'application/json' })
    res.end(JSON.stringify({ jsonrpc: '2.0', id: 1, result: { ok: true } }))
    return
  }
  res.writeHead(200, { 'content-type': 'text/event-stream' })
  res.write('data: {"jsonrpc":"2.0","id":2,"result":{"sse":true}}\n\n')
  res.write('data: {"jsonrpc":"2.0","method":"notifications/progress","params":{}}\n\n')
  res.end()
})
await new Promise<void>((r) => upstream.listen(0, '127.0.0.1', r))
const port = (upstream.address() as AddressInfo).port

await upsertRoutes({
  'claude-code/mcp/test': { upstream: `http://127.0.0.1:${port}`, decoder: 'mcp' },
})
await initRoutesTable()

const app = new Hono()
app.all('/wire-mcp/:agent/:server/*', handleMcpRelay)
app.all('/wire-mcp/:agent/:server', handleMcpRelay)

afterAll(() => {
  upstream.close()
  stopWatcher()
  closeDb()
  rmSync(tmp, { recursive: true, force: true })
})

async function mcpRows(): Promise<Array<{ transport: string; direction: string }>> {
  const raw = await getRawDb()
  const rows = raw
    .prepare(
      `SELECT json_extract(p.payload,'$.transport') AS transport,
              json_extract(p.payload,'$.direction') AS direction
       FROM actions a JOIN action_payloads p ON p.action_id = a.id
       WHERE a.kind = 'mcp_call'`,
    )
    .all() as Array<{ transport: string; direction: string }>
  return rows
}

async function waitFor(pred: () => Promise<boolean>, ms = 2000): Promise<void> {
  const deadline = Date.now() + ms
  while (Date.now() < deadline) {
    if (await pred()) return
    await new Promise((r) => setTimeout(r, 25))
  }
  throw new Error('timeout waiting for condition')
}

describe('MCP HTTP/SSE relay', () => {
  it('forwards a POST and captures request + response frames (http)', async () => {
    const res = await app.request('/wire-mcp/claude-code/test', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'x' } }),
    })
    expect(res.status).toBe(200)
    const body = (await res.json()) as { result: { ok: boolean } }
    expect(body.result.ok).toBe(true) // forwarded faithfully

    await waitFor(async () => {
      const rows = await mcpRows()
      const http = rows.filter((r) => r.transport === 'http')
      return (
        http.some((r) => r.direction === 'request') && http.some((r) => r.direction === 'response')
      )
    })
  })

  it('forwards an SSE GET and captures each event as a response/notification frame', async () => {
    const before = (await mcpRows()).length
    const res = await app.request('/wire-mcp/claude-code/test', { method: 'GET' })
    expect(res.status).toBe(200)
    expect(res.headers.get('content-type')).toContain('text/event-stream')
    await res.text() // drain the client branch

    await waitFor(async () => (await mcpRows()).length >= before + 2)
    const sse = (await mcpRows()).filter((r) => r.transport === 'sse')
    expect(sse.length).toBeGreaterThanOrEqual(2)
    expect(sse.some((r) => r.direction === 'notification')).toBe(true)
  })

  it('returns 502 for an unknown MCP server (no route)', async () => {
    const res = await app.request('/wire-mcp/claude-code/nope', { method: 'POST', body: '{}' })
    expect(res.status).toBe(502)
  })
})
