import 'dotenv/config'
import Fastify from 'fastify'
import cors from '@fastify/cors'
import jwt from '@fastify/jwt'
import websocket from '@fastify/websocket'
import multipart from '@fastify/multipart'
import { authRoutes } from './routes/auth.js'
import { integrationRoutes } from './routes/integrations.js'
import { bookmarkRoutes } from './routes/bookmarks.js'
import { eventRoutes } from './routes/events.js'
import { terminalRoutes } from './routes/terminal.js'
import { tunnelRoutes } from './routes/tunnels.js'
import { fileRoutes } from './routes/files.js'
import { dockerRoutes, dockerExecRoutes } from './routes/docker.js'
import { dockerSshRoutes, dockerSshExecRoutes } from './routes/dockerSsh.js'
import { agentIngestRoutes, agentRoutes } from './routes/agents.js'
import { guacamoleRoutes } from './routes/guacamole.js'
import { metricsRoutes } from './routes/metrics.js'
import { transferRoutes } from './routes/transfer.js'
import { dataRoutes } from './routes/data.js'
import { systemRoutes } from './routes/system.js'
import { startAutoStartTunnels } from './tunnels/manager.js'
import { db } from './db/index.js'

const JWT_SECRET = process.env.ARCHNEST_JWT_SECRET
if (!JWT_SECRET) {
  throw new Error('ARCHNEST_JWT_SECRET env var is required')
}

const app = Fastify({ logger: true })

await app.register(cors, { origin: process.env.ARCHNEST_CORS_ORIGIN ?? true })
await app.register(jwt, { secret: JWT_SECRET })
await app.register(websocket)
await app.register(multipart, { limits: { fileSize: 1024 * 1024 * 1024 } })

app.decorate('authenticate', async function (req, reply) {
  try {
    await req.jwtVerify()
  } catch {
    reply.code(401).send({ error: 'Unauthorized' })
    return
  }
  // Session-aware check: if the token carries a session id, that session must still
  // exist (so "Sign out" / revoking a session genuinely invalidates the token, not
  // just relies on the JWT signature). Tokens minted before sessions existed have no
  // `sid` and remain valid until they expire, for backward compatibility.
  const payload = req.user as { sub: number; sid?: string }
  if (payload.sid) {
    const session = db.prepare('SELECT id FROM sessions WHERE id = ? AND user_id = ?').get(payload.sid, payload.sub)
    if (!session) {
      reply.code(401).send({ error: 'Session expired' })
      return
    }
    db.prepare("UPDATE sessions SET last_seen_at = datetime('now') WHERE id = ?").run(payload.sid)
  }
  // The user must still exist and be active. Read role fresh from the DB rather than
  // trusting the (possibly stale) JWT claim — a demoted/deactivated user shouldn't keep
  // elevated access just because their token was minted earlier. Stash it on req.user.
  const dbUser = db.prepare('SELECT role, active FROM users WHERE id = ?').get(payload.sub) as
    | { role: string; active: number }
    | undefined
  if (!dbUser || dbUser.active !== 1) {
    reply.code(401).send({ error: 'Account is inactive' })
    return
  }
  ;(req.user as { role?: string }).role = dbUser.role
})

app.decorate('requireAdmin', async function (req, reply) {
  await app.authenticate(req, reply)
  if (reply.sent) return
  if ((req.user as { role?: string }).role !== 'admin') {
    reply.code(403).send({ error: 'Admin access required' })
  }
})

// For routes already behind a plugin-level `authenticate` onRequest hook: assumes
// authentication ran first (so req.user.role is populated) and only enforces the role.
app.decorate('adminOnly', async function (req, reply) {
  if ((req.user as { role?: string } | undefined)?.role !== 'admin') {
    reply.code(403).send({ error: 'Admin access required' })
  }
})

await app.register(authRoutes)
await app.register(integrationRoutes)
await app.register(bookmarkRoutes)
await app.register(eventRoutes)
await app.register(terminalRoutes)
await app.register(tunnelRoutes)
await app.register(fileRoutes)
await app.register(dockerRoutes)
await app.register(dockerExecRoutes)
await app.register(dockerSshRoutes)
await app.register(dockerSshExecRoutes)
await app.register(agentIngestRoutes)
await app.register(agentRoutes)
await app.register(guacamoleRoutes)
await app.register(metricsRoutes)
await app.register(transferRoutes)
await app.register(dataRoutes)
await app.register(systemRoutes)

app.get('/api/health', async () => ({ ok: true }))

const port = Number(process.env.PORT ?? 4000)
app.listen({ port, host: '0.0.0.0' }).catch((err) => {
  app.log.error(err)
  process.exit(1)
})

startAutoStartTunnels()
