import { eq }                         from "drizzle-orm"
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { z }                          from "zod"
import { db }                          from "../db/index.js"
import { servers }                     from "../db/schema.js"
import { MikrotikClient }              from "../services/mikrotik.js"
import { ServerIdParamSchema, type ServerIdParams }         from "../types/server.js"
import type {
  RosIpAddress, RosInterface, RosResource, RosIdentity, RosIpRoute,
  RosFirewallFilter, RosLogEntry, RosBgpSession, RosPingResult,
  RosOspfNeighbor, RosOspfArea, RosOspfInstance, RosOspfInterfaceTemplate,
  RosBfdSession,
} from "../types/server.js"

// ── string helpers ─────────────────────────────────────────────────────────────

function p(s: string | undefined, len: number): string {
  return (s ?? "").padEnd(len)
}
function flag(val: string | undefined, char: string): string {
  return val === "true" ? char : " "
}

// ── formatters ─────────────────────────────────────────────────────────────────

function fmtIpAddresses(addrs: RosIpAddress[]): string {
  const hdr = [
    "Flags: X - disabled, I - invalid, D - dynamic",
    " #   ADDRESS            NETWORK         INTERFACE",
  ]
  const rows = addrs.map((a, i) => {
    const f = `${flag(a.disabled,"X")}${flag(a.invalid,"I")}${flag(a.dynamic,"D")}`
    return ` ${String(i).padStart(2)} ${f} ${p(a.address,18)} ${p(a.network,15)} ${a.interface}${a.comment ? `  ; ${a.comment}` : ""}`
  })
  return [...hdr, ...rows].join("\n")
}

function fmtInterfaces(ifaces: RosInterface[]): string {
  const hdr = [
    "Flags: X - disabled, D - dynamic, R - running",
    " #   NAME                TYPE         MTU    MAC-ADDRESS",
  ]
  const rows = ifaces.map((f, i) => {
    const flags = `${flag(f.disabled,"X")} ${flag(f.running,"R")}`
    const name  = p(f.name, 20)
    const type  = p(f.type, 12)
    const mtu   = p(f["actual-mtu"] ?? f.mtu, 6)
    const mac   = f["mac-address"] ?? ""
    return ` ${String(i).padStart(2)} ${flags} ${name} ${type} ${mtu} ${mac}${f.comment ? `  ; ${f.comment}` : ""}`
  })
  return [...hdr, ...rows].join("\n")
}

function fmtResource(r: RosResource, id: RosIdentity): string {
  const kv = (k: string, v: string) => `${k.padStart(30)}: ${v}`
  const mib = (b: string | undefined) => b ? `${(parseInt(b) / 1024 / 1024).toFixed(1)} MiB` : "?"
  return [
    kv("uptime",           r["uptime"]            ?? "?"),
    kv("version",          r["version"]            ?? "?"),
    kv("build-time",       r["build-time"]         ?? "?"),
    kv("free-memory",      mib(r["free-memory"])),
    kv("total-memory",     mib(r["total-memory"])),
    kv("cpu",              r["cpu"]               ?? "?"),
    kv("cpu-count",        r["cpu-count"]          ?? "?"),
    kv("cpu-frequency",    r["cpu-frequency"] ? `${r["cpu-frequency"]} MHz` : "?"),
    kv("cpu-load",         r["cpu-load"] ? `${r["cpu-load"]}%` : "?"),
    kv("free-hdd-space",   mib(r["free-hdd-space"])),
    kv("total-hdd-space",  mib(r["total-hdd-space"])),
    kv("architecture-name",r["architecture-name"] ?? "?"),
    kv("board-name",       r["board-name"]         ?? "?"),
    kv("platform",         r["platform"]           ?? "MikroTik"),
    kv("identity",         id.name),
  ].join("\n")
}

function fmtIpRoutes(routes: RosIpRoute[]): string {
  const hdr = [
    "Flags: X - disabled, A - active, D - dynamic, C - connect, S - static,",
    "       r - rip, b - bgp, o - ospf, B - blackhole, U - unreachable, P - prohibit",
    " #      DST-ADDRESS        PREF-SRC        GATEWAY          DIST",
  ]
  const rows = routes.map((r, i) => {
    const f = [
      flag(r.active,      "A"),
      flag(r.dynamic,     "D"),
      flag(r.connect,     "C"),
      flag(r.static,      "S"),
      flag(r.bgp,         "b"),
      flag(r.ospf,        "o"),
      flag(r.blackhole,   "B"),
      flag(r.unreachable, "U"),
      flag(r.prohibit,    "P"),
    ].join("")
    const dst  = p(r["dst-address"] ?? "", 18)
    const pref = p(r["pref-src"]    ?? "", 15)
    const gw   = p(r["gateway"] ?? r["interface"] ?? "", 16)
    const dist = r["distance"] ?? "?"
    return ` ${String(i).padStart(2)} ${f} ${dst} ${pref} ${gw} ${dist}`
  })
  return [...hdr, ...rows].join("\n")
}

function fmtBgpSessions(sessions: RosBgpSession[]): string {
  const hdr = [
    "Flags: E - established",
    " #   NAME                    REMOTE-AS  REMOTE-ADDRESS         STATE        UPTIME",
  ]
  const rows = sessions.map((s, i) => {
    const est   = s["established"] === "true" || s["state"] === "established"
    const fl    = est ? "E" : " "
    const name  = p(s["name"] ?? s[".id"], 23)
    const remAs = p(s["remote.as"], 10)
    const remIp = p((s["remote.address"] ?? "").split("/")[0], 22)
    const state = est ? "established" : (s["state"] ?? "active")
    const up    = s["uptime"] ?? "—"
    return ` ${String(i).padStart(2)} ${fl} ${name} ${remAs} ${remIp} ${p(state, 12)} ${up}`
  })
  return [...hdr, ...rows].join("\n")
}

function fmtOspfNeighbors(neighbors: RosOspfNeighbor[]): string {
  const hdr = [
    "Flags: V - virtual",
    " #   ROUTER-ID        STATE    CHANGES  ADJACENCY       INTERFACE",
  ]
  const rows = neighbors.map((n, i) => {
    return ` ${String(i).padStart(2)}  ${p(n["router-id"],16)} ${p(n["state"],8)} ${p(n["state-changes"] ?? "0",8)} ${p(n["adjacency"] ?? "—",15)} ${n["interface"] ?? ""}`
  })
  return [...hdr, ...rows].join("\n")
}

function fmtOspfAreas(areas: RosOspfArea[]): string {
  const hdr = [" #   NAME                 AREA-ID         TYPE         INSTANCE"]
  const rows = areas.map((a, i) =>
    ` ${String(i).padStart(2)}  ${p(a.name,20)} ${p(a["area-id"] ?? "0.0.0.0",15)} ${p(a.type ?? "default",12)} ${a.instance}`
  )
  return [...hdr, ...rows].join("\n")
}

function fmtOspfInstances(insts: RosOspfInstance[]): string {
  const hdr = [" #   NAME            ROUTER-ID          VER  DISTRIBUTE"]
  const rows = insts.map((inst, i) =>
    ` ${String(i).padStart(2)}  ${p(inst.name,15)} ${p(inst["router-id"],18)} ${p(inst.version ?? "2",4)} ${inst["redistribute"] ?? "—"}`
  )
  return [...hdr, ...rows].join("\n")
}

function fmtOspfIfaceTemplates(templates: RosOspfInterfaceTemplate[]): string {
  const hdr = [" #   INTERFACES            AREA                COST  TYPE       BFD"]
  const rows = templates.map((t, i) =>
    ` ${String(i).padStart(2)}  ${p(t.interfaces ?? "—",21)} ${p(t.area,19)} ${p(t.cost ?? "10",5)} ${p(t.type ?? "broadcast",10)} ${t["use-bfd"] === "true" ? "yes" : "no"}`
  )
  return [...hdr, ...rows].join("\n")
}

function fmtBfdSessions(sessions: RosBfdSession[]): string {
  const hdr = [" #   LOCAL-ADDRESS               REMOTE-ADDRESS              STATE    UPTIME"]
  const rows = sessions.map((s, i) =>
    ` ${String(i).padStart(2)}  ${p(s["local-address"],27)} ${p(s["remote-address"],27)} ${p(s["state"] ?? "down",8)} ${s["uptime"] ?? "—"}`
  )
  return [...hdr, ...rows].join("\n")
}

function fmtFirewallFilter(rules: RosFirewallFilter[]): string {
  const lines: string[] = ["Flags: X - disabled, I - invalid, D - dynamic"]
  rules.forEach((r, i) => {
    const f = `${flag(r.disabled,"X")}${flag(r.invalid,"I")}${flag(r.dynamic,"D")}`
    lines.push(``)
    lines.push(` ${String(i).padStart(2)} ${f}`)
    if (r.comment) lines.push(`      ;;; ${r.comment}`)
    const parts = [`chain=${r.chain}`, `action=${r.action}`]
    if (r.protocol)           parts.push(`protocol=${r.protocol}`)
    if (r["connection-state"])parts.push(`connection-state=${r["connection-state"]}`)
    if (r["src-address"])     parts.push(`src-address=${r["src-address"]}`)
    if (r["dst-address"])     parts.push(`dst-address=${r["dst-address"]}`)
    if (r["src-address-list"])parts.push(`src-address-list=${r["src-address-list"]}`)
    if (r["dst-address-list"])parts.push(`dst-address-list=${r["dst-address-list"]}`)
    if (r["src-port"])        parts.push(`src-port=${r["src-port"]}`)
    if (r["dst-port"])        parts.push(`dst-port=${r["dst-port"]}`)
    if (r["in-interface"])    parts.push(`in-interface=${r["in-interface"]}`)
    if (r["out-interface"])   parts.push(`out-interface=${r["out-interface"]}`)
    if (r["tls-host"])        parts.push(`tls-host=${r["tls-host"]}`)
    lines.push(`      ${parts.join(" ")}`)
    if (r.packets || r.bytes) lines.push(`      packets=${r.packets ?? "0"} bytes=${r.bytes ?? "0"}`)
  })
  return lines.join("\n")
}

function fmtLogs(entries: RosLogEntry[]): string {
  if (entries.length === 0) return "(no log entries)"
  return entries.map(e =>
    `${(e.time ?? "").padEnd(20)} ${(e.topics ?? "").padEnd(28)} ${e.message ?? ""}`
  ).join("\n")
}

function fmtPing(results: RosPingResult[], host: string): string {
  const lines = [`PING ${host}`]
  // individual replies
  for (const r of results) {
    if (!r.time && !r.ttl) continue   // skip summary-only items
    if (r.status === "timeout") {
      lines.push(`  seq=${r.seq} timeout`)
    } else {
      lines.push(`  seq=${r.seq} ttl=${r.ttl ?? "?"} time=${r.time ?? "?"}`)
    }
  }
  // summary — last item with sent/received fields
  const sum = [...results].reverse().find(r => r.sent)
  if (sum) {
    const loss = sum["packet-loss"] ?? "0%"
    lines.push(`  sent=${sum.sent} received=${sum.received} packet-loss=${loss}`)
    if (sum.time) lines.push(`  avg-rtt=${sum.time}`)
  }
  return lines.join("\n")
}

// ── help text ──────────────────────────────────────────────────────────────────

const HELP_TEXT = [
  "Доступные команды (RouterOS REST API):",
  "  ip address print                  — IP-адреса",
  "  ip route print                    — таблица маршрутов",
  "  interface print                   — интерфейсы",
  "  system resource print             — ресурсы системы",
  "  system identity print             — имя роутера",
  "  ip firewall filter print          — правила firewall",
  "  routing bgp session print         — BGP-сессии",
  "  routing ospf neighbor print       — OSPF-соседи",
  "  routing ospf area print           — OSPF-зоны",
  "  routing ospf instance print       — OSPF-инстансы",
  "  routing ospf interface print      — OSPF-интерфейсы",
  "  routing bfd session print         — BFD-сессии",
  "  log print [limit=N]               — системный лог",
  "  ping <host> [count=N]             — пинг",
].join("\n")

// ── command dispatcher ─────────────────────────────────────────────────────────

async function dispatch(client: MikrotikClient, raw: string): Promise<string> {
  // strip leading slashes, normalize whitespace
  const cmd   = raw.trim().replace(/^\/+/, "")
  const lower = cmd.toLowerCase().replace(/\s+/g, " ")

  if (lower === "?" || lower === "help") return HELP_TEXT

  if (lower === "ip address print")
    return fmtIpAddresses(await client.getIpAddresses())

  if (lower === "interface print")
    return fmtInterfaces(await client.getInterfaces())

  if (lower === "system resource print") {
    const [res, id] = await Promise.all([client.getResource(), client.getIdentity()])
    return fmtResource(res, id)
  }

  if (lower === "system identity print")
    return `  name: ${(await client.getIdentity()).name}`

  if (lower === "ip route print")
    return fmtIpRoutes(await client.getIpRoutes())

  if (lower === "ip firewall filter print")
    return fmtFirewallFilter(await client.getFirewallFilters())

  if (lower === "routing bgp session print" || lower === "bgp session print" || lower === "bgp peer print")
    return fmtBgpSessions(await client.getBgpSessions())

  if (lower === "routing ospf neighbor print" || lower === "ospf neighbor print")
    return fmtOspfNeighbors(await client.getOspfNeighbors())

  if (lower === "routing ospf area print" || lower === "ospf area print")
    return fmtOspfAreas(await client.getOspfAreas())

  if (lower === "routing ospf instance print" || lower === "ospf instance print")
    return fmtOspfInstances(await client.getOspfInstances())

  if (lower === "routing ospf interface print" || lower === "routing ospf interface-template print")
    return fmtOspfIfaceTemplates(await client.getOspfInterfaceTemplates())

  if (lower === "routing bfd session print" || lower === "bfd session print")
    return fmtBfdSessions(await client.getBfdSessions().catch(() => []))

  if (lower.startsWith("log print")) {
    const limitArg = lower.match(/limit=(\d+)/)
    const limit    = limitArg ? parseInt(limitArg[1]) : 50
    return fmtLogs(await client.getLogs(Math.min(limit, 200)))
  }

  if (lower.startsWith("ping")) {
    const parts    = cmd.split(/\s+/)
    const host     = parts[1]
    if (!host) return "Usage: ping <address> [count=N]"
    const countArg = parts.find(p => p.toLowerCase().startsWith("count="))
    const count    = countArg ? parseInt(countArg.split("=")[1]) : 4
    const results  = await client.ping(host, Math.min(count, 20))
    return fmtPing(results, host)
  }

  const firstToken = cmd.split(" ")[0]
  return `bad command name ${firstToken} (line 1 column 1)`
}

// ── route plugin ───────────────────────────────────────────────────────────────

const ExecBodySchema = z.object({
  command: z.string().min(1).max(1024),
})

const execRoutes: FastifyPluginAsyncZod = async (app) => {
  // POST /api/servers/:id/exec  — execute a RouterOS CLI-style command via REST API
  app.post(
    "/servers/:id/exec",
    { schema: { params: ServerIdParamSchema, body: ExecBodySchema } },
    async (req, reply) => {
      const params = req.params as ServerIdParams
      const server = db
        .select().from(servers)
        .where(eq(servers.id, params.id))
        .limit(1).all()[0]

      if (!server) return reply.status(404).send({ error: "Server not found" })

      const client = MikrotikClient.fromServer(server)
      try {
        const output = await dispatch(client, req.body.command)
        return reply.send({ output })
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err)
        // Return as output (not HTTP error) so the terminal can display it
        return reply.send({ output: `error: ${msg}` })
      }
    }
  )
}

export default execRoutes
