import { asc, eq } from "drizzle-orm"
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { db } from "../db/index.js"
import { recursiveRoutes, servers } from "../db/schema.js"
import { MikrotikClient } from "../services/mikrotik.js"
import { parseDbServerId } from "../utils/server-id.js"
import {
  hasManagedRecursiveComment,
  managedRecursiveComment,
} from "../managed-markers.js"

type ServerRow = typeof servers.$inferSelect

interface RosRoute {
  ".id"?: string
  "dst-address"?: string
  gateway?: string
  distance?: string
  scope?: string
  "target-scope"?: string
  "routing-table"?: string
  "check-gateway"?: string
  comment?: string
  disabled?: string
  static?: string
  dynamic?: string
  blackhole?: string
  unreachable?: string
  prohibit?: string
  active?: string
}

interface GatewayOptionDto {
  id: string
  name: string
  ip: string
  status: "up" | "down"
}

interface RecursiveRouteDto {
  id: string
  dstAddress: string
  gateway: string
  distance: number
  scope: number | null
  targetScope: number | null
  routingTable: string
  checkGateway: string
  country: string
  comment: string
  disabled: boolean
}

function isIpGateway(gw: string): boolean {
  return /^\d{1,3}(\.\d{1,3}){3}(?:%\S+)?$/.test(gw.trim())
}

function isRecursiveRoute(r: RosRoute): boolean {
  if ((r.static ?? "false") !== "true") return false
  if ((r.dynamic ?? "false") === "true") return false
  if ((r.blackhole ?? "false") === "true") return false
  if ((r.unreachable ?? "false") === "true") return false
  if ((r.prohibit ?? "false") === "true") return false
  const dst = r["dst-address"] ?? ""
  const gw = r.gateway ?? ""
  if (!dst || !gw) return false
  return isIpGateway(gw)
}

function hasRecursiveCommentMask(comment: string | undefined): boolean {
  if (!comment) return false
  return /^recursive:\s*/i.test(comment.trim())
}

function splitGateway(raw: string): { ip: string; name: string } | null {
  const v = raw.trim()
  if (!v) return null
  const [ip, name] = v.split("%")
  if (!ip || !/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) return null
  return { ip, name: name || ip }
}

function mapDbRoutes(serverId: number): RecursiveRouteDto[] {
  const rows = db
    .select()
    .from(recursiveRoutes)
    .where(eq(recursiveRoutes.serverId, serverId))
    .orderBy(asc(recursiveRoutes.sortOrder))
    .all()

  return rows.map((r) => ({
    id: String(r.id),
    dstAddress: r.dstAddress,
    gateway: r.gateway,
    distance: r.distance,
    scope: r.scope ?? null,
    targetScope: r.targetScope ?? null,
    routingTable: r.routingTable,
    checkGateway: r.checkGateway,
    country: r.country ?? "",
    comment: r.comment,
    disabled: r.disabled,
  }))
}

function toRouterPayload(route: RecursiveRouteDto): Record<string, string> {
  return {
    "dst-address": route.dstAddress,
    gateway: route.gateway,
    distance: String(route.distance),
    ...(route.scope != null ? { scope: String(route.scope) } : {}),
    ...(route.targetScope != null ? { "target-scope": String(route.targetScope) } : {}),
    ...(route.routingTable ? { "routing-table": route.routingTable } : {}),
    ...(route.checkGateway ? { "check-gateway": route.checkGateway } : {}),
    comment: managedRecursiveComment(route.comment),
    disabled: route.disabled ? "true" : "false",
  }
}

async function replaceDbRoutes(serverId: number, routes: RecursiveRouteDto[]) {
  db.delete(recursiveRoutes).where(eq(recursiveRoutes.serverId, serverId)).run()
  if (routes.length === 0) return
  const now = new Date().toISOString()
  db.insert(recursiveRoutes).values(
    routes.map((r, i) => ({
      serverId,
      sortOrder: i,
      dstAddress: r.dstAddress,
      gateway: r.gateway,
      distance: r.distance,
      scope: r.scope,
      targetScope: r.targetScope,
      routingTable: r.routingTable || "main",
      checkGateway: r.checkGateway ?? "",
      country: r.country ?? "",
      comment: r.comment ?? "",
      disabled: r.disabled,
      createdAt: now,
      updatedAt: now,
    })),
  ).run()
}

const recursiveRoutesPlugin: FastifyPluginAsyncZod = async (app) => {
  app.get("/recursive-routes/gateways", async (req, reply) => {
    const q = req.query as { serverId?: string | number }
    const serverId = parseDbServerId(q.serverId)
    if (serverId === null) return reply.status(400).send({ error: "serverId is required" })
    const server = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })

    try {
      const client = MikrotikClient.fromServer(server)
      const rosRoutes = await client.get<RosRoute[]>("/ip/route")
      const map = new Map<string, GatewayOptionDto>()
      for (const r of rosRoutes) {
        const g = splitGateway(r.gateway ?? "")
        if (!g) continue
        const id = `${g.ip}%${g.name}`
        if (!map.has(id)) {
          map.set(id, {
            id,
            name: g.name,
            ip: g.ip,
            status: r.active === "true" ? "up" : "down",
          })
        }
      }
      return reply.send({ gateways: [...map.values()] })
    } catch (err) {
      return reply.status(500).send({ error: String(err) })
    }
  })

  app.get("/recursive-routes", async (req, reply) => {
    const q = req.query as { serverId?: string | number }
    const serverId = parseDbServerId(q.serverId)
    if (serverId === null) return reply.status(400).send({ error: "serverId is required" })
    const server = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })
    return reply.send({ routes: mapDbRoutes(serverId) })
  })

  app.put("/recursive-routes", async (req, reply) => {
    const body = req.body as { serverId?: string | number; routes?: RecursiveRouteDto[] }
    const serverId = parseDbServerId(body.serverId)
    if (serverId === null) return reply.status(400).send({ error: "serverId is required" })
    const server = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })
    await replaceDbRoutes(serverId, body.routes ?? [])
    return reply.send({ ok: true })
  })

  app.post("/recursive-routes/sync/from-router", async (req, reply) => {
    const body = req.body as { serverId?: string | number } | undefined
    const serverId = parseDbServerId(body?.serverId)
    if (serverId === null) return reply.status(400).send({ error: "serverId is required" })
    const server: ServerRow | undefined = db
      .select().from(servers)
      .where(eq(servers.id, serverId))
      .limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })

    try {
      const client = MikrotikClient.fromServer(server)
      const rosRoutes = await client.get<RosRoute[]>("/ip/route")
      const rec = rosRoutes.filter(r =>
        isRecursiveRoute(r) && hasRecursiveCommentMask(r.comment),
      )
      const mapped: RecursiveRouteDto[] = rec.map((r, i) => ({
        id: r[".id"] ?? `ros-${i}`,
        dstAddress: r["dst-address"] ?? "",
        gateway: r.gateway ?? "",
        distance: Number.parseInt(r.distance ?? "1", 10) || 1,
        scope: r.scope ? (Number.parseInt(r.scope, 10) || null) : null,
        targetScope: r["target-scope"] ? (Number.parseInt(r["target-scope"], 10) || null) : null,
        routingTable: r["routing-table"] ?? "main",
        checkGateway: r["check-gateway"] ?? "",
        country: "",
        comment: r.comment ?? "",
        disabled: r.disabled === "true",
      }))
      await replaceDbRoutes(serverId, mapped)
      return reply.send({ ok: true, serverId, totalRoutes: mapped.length })
    } catch (err) {
      return reply.status(500).send({ error: String(err) })
    }
  })

  app.post("/recursive-routes/sync/to-router", async (req, reply) => {
    const body = req.body as { serverId?: string | number } | undefined
    const serverId = parseDbServerId(body?.serverId)
    if (serverId === null) return reply.status(400).send({ error: "serverId is required" })
    const server = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })

    try {
      const client = MikrotikClient.fromServer(server)
      const existing = await client.get<RosRoute[]>("/ip/route")
      const managed = existing.filter(r => hasManagedRecursiveComment(r.comment ?? ""))
      for (const r of managed) {
        if (!r[".id"]) continue
        await client.delete(`/ip/route/${encodeURIComponent(r[".id"])}`)
      }

      const dbRows = mapDbRoutes(serverId)
      for (const route of dbRows) {
        await client.post("/ip/route", toRouterPayload(route))
      }

      return reply.send({ ok: true, serverId, pushedRoutes: dbRows.length })
    } catch (err) {
      return reply.status(500).send({ error: String(err) })
    }
  })
}

export default recursiveRoutesPlugin
