import type { FastifyInstance } from 'fastify'
import { z } from 'zod'
import { loadDockerHost, dockerFetch, dockerJson, demuxDockerStream } from '../docker/client.js'
import { openExecStream, stripHijackHeaders, resizeExec } from '../docker/exec.js'

interface ExecMessage {
  type: 'connect' | 'input' | 'resize' | 'disconnect'
  integrationId?: number
  containerId?: string
  cols?: number
  rows?: number
  data?: string
}

function sendJson(socket: { send: (data: string) => void }, payload: Record<string, unknown>) {
  socket.send(JSON.stringify(payload))
}

interface ContainerSummary {
  Id: string
  Names: string[]
  Image: string
  State: string
  Status: string
  Ports: { PrivatePort: number; PublicPort?: number; Type: string }[]
}

function serializeContainer(c: ContainerSummary) {
  return {
    id: c.Id,
    name: c.Names[0]?.replace(/^\//, '') ?? c.Id.slice(0, 12),
    image: c.Image,
    state: c.State,
    status: c.Status,
    ports: c.Ports.map((p) => ({ privatePort: p.PrivatePort, publicPort: p.PublicPort, type: p.Type })),
  }
}

export async function dockerExecRoutes(app: FastifyInstance) {
  app.get('/api/docker/exec', { websocket: true }, (socket, req) => {
    let execSocket: Awaited<ReturnType<typeof openExecStream>>['socket'] | null = null
    let host: ReturnType<typeof loadDockerHost> = null
    let execId: string | null = null

    const cleanup = () => {
      execSocket?.destroy()
      execSocket = null
    }
    socket.on('close', cleanup)

    socket.on('message', async (raw: Buffer) => {
      let msg: ExecMessage
      try {
        msg = JSON.parse(raw.toString())
      } catch {
        sendJson(socket, { type: 'error', message: 'Invalid JSON' })
        return
      }

      if (msg.type === 'connect') {
        const query = req.query as { token?: string }
        try {
          await app.jwt.verify(query.token ?? '')
        } catch {
          sendJson(socket, { type: 'error', message: 'Unauthorized' })
          socket.close()
          return
        }

        host = msg.integrationId !== undefined ? loadDockerHost(msg.integrationId) : null
        if (!host || !msg.containerId) {
          sendJson(socket, { type: 'error', message: 'Docker integration or container not found' })
          return
        }

        try {
          const result = await openExecStream(host, msg.containerId, msg.cols ?? 80, msg.rows ?? 24)
          execSocket = result.socket
          execId = result.execId
          const forward = stripHijackHeaders((chunk) => {
            socket.send(JSON.stringify({ type: 'data', data: chunk.toString('base64') }))
          })
          execSocket.on('data', forward)
          execSocket.on('error', (err: Error) => sendJson(socket, { type: 'error', message: err.message }))
          execSocket.on('close', () => sendJson(socket, { type: 'exit' }))
          sendJson(socket, { type: 'ready' })
        } catch (err) {
          sendJson(socket, { type: 'error', message: err instanceof Error ? err.message : 'Failed to start exec session' })
        }
        return
      }

      if (msg.type === 'input' && execSocket && msg.data !== undefined) {
        execSocket.write(Buffer.from(msg.data, 'base64'))
        return
      }

      if (msg.type === 'resize' && host && execId && msg.cols && msg.rows) {
        try {
          await resizeExec(host, execId, msg.cols, msg.rows)
        } catch {
          // best-effort - a failed resize shouldn't kill the session
        }
        return
      }

      if (msg.type === 'disconnect') {
        cleanup()
        socket.close()
      }
    })
  })
}

export async function dockerRoutes(app: FastifyInstance) {
  app.addHook('onRequest', app.authenticate)

  app.get('/api/docker/:integrationId/containers', async (req, reply) => {
    const integrationId = Number((req.params as { integrationId: string }).integrationId)
    const host = loadDockerHost(integrationId)
    if (!host) return reply.code(404).send({ error: 'Docker integration not found' })
    try {
      const containers = await dockerJson<ContainerSummary[]>(host, '/containers/json?all=true')
      return { containers: containers.map(serializeContainer) }
    } catch (err) {
      return reply.code(400).send({ error: err instanceof Error ? err.message : 'Failed to list containers' })
    }
  })

  app.get('/api/docker/:integrationId/containers/:id/stats', async (req, reply) => {
    const { integrationId, id } = req.params as { integrationId: string; id: string }
    const host = loadDockerHost(Number(integrationId))
    if (!host) return reply.code(404).send({ error: 'Docker integration not found' })
    try {
      const stats = await dockerJson<{
        cpu_stats: { cpu_usage: { total_usage: number }; system_cpu_usage: number; online_cpus?: number }
        precpu_stats: { cpu_usage: { total_usage: number }; system_cpu_usage: number }
        memory_stats: { usage?: number; limit?: number }
        networks?: Record<string, { rx_bytes: number; tx_bytes: number }>
      }>(host, `/containers/${id}/stats?stream=false`)

      const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage
      const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage
      const cpuCount = stats.cpu_stats.online_cpus || 1
      const cpuPercent = systemDelta > 0 && cpuDelta > 0 ? (cpuDelta / systemDelta) * cpuCount * 100 : 0

      const netRx = Object.values(stats.networks ?? {}).reduce((sum, n) => sum + n.rx_bytes, 0)
      const netTx = Object.values(stats.networks ?? {}).reduce((sum, n) => sum + n.tx_bytes, 0)

      return {
        cpuPercent: Math.round(cpuPercent * 10) / 10,
        memUsage: stats.memory_stats.usage ?? 0,
        memLimit: stats.memory_stats.limit ?? 0,
        netRx,
        netTx,
      }
    } catch (err) {
      return reply.code(400).send({ error: err instanceof Error ? err.message : 'Failed to fetch stats' })
    }
  })

  app.get('/api/docker/:integrationId/containers/:id/logs', async (req, reply) => {
    const { integrationId, id } = req.params as { integrationId: string; id: string }
    const tail = (req.query as { tail?: string }).tail ?? '200'
    const host = loadDockerHost(Number(integrationId))
    if (!host) return reply.code(404).send({ error: 'Docker integration not found' })
    try {
      const res = await dockerFetch(host, `/containers/${id}/logs?stdout=true&stderr=true&tail=${encodeURIComponent(tail)}`)
      const buf = Buffer.from(await res.arrayBuffer())
      return { logs: demuxDockerStream(buf) }
    } catch (err) {
      return reply.code(400).send({ error: err instanceof Error ? err.message : 'Failed to fetch logs' })
    }
  })

  const actionSchema = z.enum(['start', 'stop', 'restart', 'pause', 'unpause'])

  app.post('/api/docker/:integrationId/containers/:id/:action', async (req, reply) => {
    const { integrationId, id, action } = req.params as { integrationId: string; id: string; action: string }
    const parsedAction = actionSchema.safeParse(action)
    if (!parsedAction.success) return reply.code(400).send({ error: 'Invalid action' })
    const host = loadDockerHost(Number(integrationId))
    if (!host) return reply.code(404).send({ error: 'Docker integration not found' })
    try {
      await dockerFetch(host, `/containers/${id}/${parsedAction.data}`, { method: 'POST' })
      return { ok: true }
    } catch (err) {
      return reply.code(400).send({ error: err instanceof Error ? err.message : `Failed to ${action} container` })
    }
  })

  app.post('/api/docker/:integrationId/containers/:id/remove', async (req, reply) => {
    const { integrationId, id } = req.params as { integrationId: string; id: string }
    const bodySchema = z.object({ force: z.boolean().default(false) })
    const parsed = bodySchema.safeParse(req.body ?? {})
    if (!parsed.success) return reply.code(400).send({ error: 'Invalid input' })
    const host = loadDockerHost(Number(integrationId))
    if (!host) return reply.code(404).send({ error: 'Docker integration not found' })
    try {
      await dockerFetch(host, `/containers/${id}?force=${parsed.data.force}`, { method: 'DELETE' })
      return { ok: true }
    } catch (err) {
      return reply.code(400).send({ error: err instanceof Error ? err.message : 'Failed to remove container' })
    }
  })
}
