import type { FastifyInstance } from 'fastify'
import { z } from 'zod'
import { db, logEvent } from '../db/index.js'
import { encryptSecret, decryptSecret } from '../db/crypto.js'

/**
 * Backup / restore of the user's configuration: integrations (with their
 * credentials), bookmarks, and tunnels. Secrets are exported *decrypted* on
 * purpose — that makes the backup portable to another ArchNest instance with a
 * different ARCHNEST_SECRET_KEY. The export therefore contains plaintext
 * credentials and should be treated as sensitive (it's only ever served to an
 * authenticated user, over the same channel they'd use to read those secrets
 * anyway via the integrations they own).
 */

const EXPORT_VERSION = 1

interface IntegrationRow {
  id: number
  type: string
  name: string
  enabled: number
  config_json: string
}
interface SecretRow {
  key: string
  value_encrypted: string
}
interface CategoryRow {
  id: number
  name: string
  icon: string | null
  sort_order: number
}
interface BookmarkRow {
  id: number
  category_id: number | null
  title: string
  url: string
  icon: string | null
  favorite: number
}
interface TunnelRow {
  id: number
  name: string
  integration_id: number
  mode: string
  source_port: number
  endpoint_host: string
  endpoint_port: number
  auto_start: number
  max_retries: number
  retry_interval_ms: number
}

const importSchema = z.object({
  version: z.number().optional(),
  integrations: z
    .array(
      z.object({
        id: z.number(),
        type: z.string(),
        name: z.string(),
        enabled: z.boolean().default(true),
        config: z.record(z.string(), z.string()).default({}),
        secrets: z.record(z.string(), z.string()).default({}),
      }),
    )
    .default([]),
  bookmarkCategories: z
    .array(z.object({ id: z.number(), name: z.string(), icon: z.string().nullable().default(null), sortOrder: z.number().default(0) }))
    .default([]),
  bookmarks: z
    .array(
      z.object({
        categoryId: z.number().nullable().default(null),
        title: z.string(),
        url: z.string(),
        icon: z.string().nullable().default(null),
        favorite: z.boolean().default(false),
      }),
    )
    .default([]),
  tunnels: z
    .array(
      z.object({
        name: z.string(),
        integrationId: z.number(),
        mode: z.string(),
        sourcePort: z.number(),
        endpointHost: z.string().default(''),
        endpointPort: z.number().default(0),
        autoStart: z.boolean().default(false),
        maxRetries: z.number().default(3),
        retryIntervalMs: z.number().default(5000),
      }),
    )
    .default([]),
})

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

  app.get('/api/data/export', async () => {
    const integrations = (db.prepare('SELECT id, type, name, enabled, config_json FROM integrations').all() as IntegrationRow[]).map((row) => {
      const secretRows = db.prepare('SELECT key, value_encrypted FROM secrets WHERE integration_id = ?').all(row.id) as SecretRow[]
      const secrets: Record<string, string> = {}
      for (const s of secretRows) {
        try {
          secrets[s.key] = decryptSecret(s.value_encrypted)
        } catch {
          // a secret we can't decrypt (wrong key) is skipped rather than failing the whole export
        }
      }
      return { id: row.id, type: row.type, name: row.name, enabled: !!row.enabled, config: JSON.parse(row.config_json), secrets }
    })

    const bookmarkCategories = (db.prepare('SELECT id, name, icon, sort_order FROM bookmark_categories').all() as CategoryRow[]).map((c) => ({
      id: c.id,
      name: c.name,
      icon: c.icon,
      sortOrder: c.sort_order,
    }))

    const bookmarks = (db.prepare('SELECT category_id, title, url, icon, favorite FROM bookmarks').all() as BookmarkRow[]).map((b) => ({
      categoryId: b.category_id,
      title: b.title,
      url: b.url,
      icon: b.icon,
      favorite: !!b.favorite,
    }))

    const tunnels = (db.prepare('SELECT name, integration_id, mode, source_port, endpoint_host, endpoint_port, auto_start, max_retries, retry_interval_ms FROM tunnels').all() as TunnelRow[]).map(
      (t) => ({
        name: t.name,
        integrationId: t.integration_id,
        mode: t.mode,
        sourcePort: t.source_port,
        endpointHost: t.endpoint_host,
        endpointPort: t.endpoint_port,
        autoStart: !!t.auto_start,
        maxRetries: t.max_retries,
        retryIntervalMs: t.retry_interval_ms,
      }),
    )

    return { version: EXPORT_VERSION, exportedAt: new Date().toISOString(), integrations, bookmarkCategories, bookmarks, tunnels }
  })

  app.post('/api/data/import', async (req, reply) => {
    const parsed = importSchema.safeParse(req.body)
    if (!parsed.success) {
      return reply.code(400).send({ error: parsed.error.issues[0]?.message ?? 'Invalid import file' })
    }
    const { integrations, bookmarkCategories, bookmarks, tunnels } = parsed.data

    const counts = { integrations: 0, bookmarkCategories: 0, bookmarks: 0, tunnels: 0 }

    // Additive import: insert everything as new rows, remapping old ids -> new ids so
    // tunnels and bookmarks keep pointing at the right (newly-created) parents.
    const runImport = db.transaction(() => {
      const integrationIdMap = new Map<number, number>()
      const insertIntegration = db.prepare('INSERT INTO integrations (type, name, enabled, config_json) VALUES (?, ?, ?, ?)')
      const insertSecret = db.prepare('INSERT INTO secrets (integration_id, key, value_encrypted) VALUES (?, ?, ?)')
      for (const i of integrations) {
        const res = insertIntegration.run(i.type, i.name, i.enabled ? 1 : 0, JSON.stringify(i.config))
        const newId = Number(res.lastInsertRowid)
        integrationIdMap.set(i.id, newId)
        for (const [key, value] of Object.entries(i.secrets)) {
          insertSecret.run(newId, key, encryptSecret(value))
        }
        counts.integrations += 1
      }

      const categoryIdMap = new Map<number, number>()
      const insertCategory = db.prepare('INSERT INTO bookmark_categories (name, icon, sort_order) VALUES (?, ?, ?)')
      for (const c of bookmarkCategories) {
        const res = insertCategory.run(c.name, c.icon, c.sortOrder)
        categoryIdMap.set(c.id, Number(res.lastInsertRowid))
        counts.bookmarkCategories += 1
      }

      const insertBookmark = db.prepare('INSERT INTO bookmarks (category_id, title, url, icon, favorite) VALUES (?, ?, ?, ?, ?)')
      for (const b of bookmarks) {
        const mappedCategory = b.categoryId !== null ? categoryIdMap.get(b.categoryId) ?? null : null
        insertBookmark.run(mappedCategory, b.title, b.url, b.icon, b.favorite ? 1 : 0)
        counts.bookmarks += 1
      }

      const insertTunnel = db.prepare(
        'INSERT INTO tunnels (name, integration_id, mode, source_port, endpoint_host, endpoint_port, auto_start, max_retries, retry_interval_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
      )
      for (const t of tunnels) {
        const mappedIntegration = integrationIdMap.get(t.integrationId)
        if (mappedIntegration === undefined) continue // tunnel referenced an integration not present in this import — skip
        insertTunnel.run(t.name, mappedIntegration, t.mode, t.sourcePort, t.endpointHost, t.endpointPort, t.autoStart ? 1 : 0, t.maxRetries, t.retryIntervalMs)
        counts.tunnels += 1
      }
    })

    runImport()
    logEvent('data_imported', `Imported ${counts.integrations} integrations, ${counts.bookmarks} bookmarks, ${counts.tunnels} tunnels`)
    return { ok: true, imported: counts }
  })
}
