import type { FastifyInstance } from 'fastify'
import { randomUUID } from 'node:crypto'
import { db } from '../db/index.js'
import { loadSecrets } from '../db/secrets.js'
// guacamole-lite only exports its Server class from the package root; the lower-level
// ClientConnection/Crypt classes we need are pulled directly from their CJS lib files.
import ClientConnection from 'guacamole-lite/lib/ClientConnection.js'
import Crypt from 'guacamole-lite/lib/Crypt.js'

interface RemoteDesktopRow {
  id: number
  type: string
  config_json: string
}

const CRYPT_CYPHER = 'AES-256-CBC'
const CRYPT_KEY = process.env.ARCHNEST_GUAC_CRYPT_KEY

const GUACD_HOST = process.env.ARCHNEST_GUACD_HOST ?? '127.0.0.1'
const GUACD_PORT = Number(process.env.ARCHNEST_GUACD_PORT ?? 4822)

const CLIENT_OPTIONS = {
  log: { level: 30, stdLog: () => {}, errorLog: () => {} },
  crypt: { cypher: CRYPT_CYPHER, key: CRYPT_KEY },
  maxInactivityTime: 0,
  connectionDefaultSettings: {
    rdp: { port: '3389', width: 1024, height: 768, dpi: 96, image: ['image/png', 'image/jpeg'] },
    vnc: { port: '5900', width: 1024, height: 768, dpi: 96, image: ['image/png', 'image/jpeg'] },
    telnet: { port: '23', width: 1024, height: 768, dpi: 96, image: ['image/png', 'image/jpeg'] },
  },
  allowedUnencryptedConnectionSettings: {
    rdp: ['width', 'height', 'dpi'],
    vnc: ['width', 'height', 'dpi'],
    telnet: ['width', 'height', 'dpi'],
  },
}

function loadRemoteDesktopTarget(integrationId: number) {
  const row = db
    .prepare("SELECT * FROM integrations WHERE id = ? AND type = 'remote_desktop'")
    .get(integrationId) as RemoteDesktopRow | undefined
  if (!row) return null
  const config = JSON.parse(row.config_json) as Record<string, string>
  const secrets = loadSecrets(row.id)
  return { config, secrets }
}

export async function guacamoleRoutes(app: FastifyInstance) {
  if (!CRYPT_KEY) {
    app.log.warn('ARCHNEST_GUAC_CRYPT_KEY not set — remote desktop sessions will fail to start')
  }

  app.get('/api/guacamole', { websocket: true }, (socket, req) => {
    const query = req.query as { token?: string; integrationId?: string }

    void (async () => {
      try {
        await app.jwt.verify(query.token ?? '')
      } catch {
        socket.close(1008, 'Unauthorized')
        return
      }

      const integrationId = Number(query.integrationId)
      const target = Number.isFinite(integrationId) ? loadRemoteDesktopTarget(integrationId) : null
      if (!target) {
        socket.close(1008, 'Remote desktop integration not found')
        return
      }
      if (!CRYPT_KEY) {
        socket.close(1011, 'Server not configured for remote desktop')
        return
      }

      const { protocol, hostname, port, username, domain } = target.config
      const settings: Record<string, unknown> = { hostname, username, password: target.secrets.password ?? '' }
      if (port) settings.port = port
      if (domain) settings.domain = domain

      const token = new Crypt(CRYPT_CYPHER, CRYPT_KEY).encrypt({
        connection: { type: protocol, settings },
      })

      const connectionId = randomUUID()
      const clientConnection = new ClientConnection(
        CLIENT_OPTIONS,
        connectionId,
        socket,
        { token },
        { processConnectionSettings: (s: unknown, cb: (err: unknown, s: unknown) => void) => cb(undefined, s) },
      )
      clientConnection.connect({ host: GUACD_HOST, port: GUACD_PORT })
    })()
  })
}
