import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { z } from "zod"
import { MikrotikClient, MikrotikError, encodeRosId, firewallRestPath } from "../services/mikrotik.js"
import { getEnabledServerById } from "../services/wireguard-live.js"
import { listFirewallAll } from "../services/firewall-live.js"
import type { FirewallFamily, FirewallTable } from "../types/server.js"

const FamilySchema = z.enum(["ip", "ip6"])
const TableSchema = z.enum(["filter", "nat", "mangle", "raw"])

const RuleKeySchema = z.object({
  serverId: z.string().min(1),
  family: FamilySchema,
  table: TableSchema,
  rosId: z.string().min(1),
})

const RuleWriteSchema = z.object({
  serverId: z.string().min(1),
  family: FamilySchema,
  table: TableSchema,
  rosId: z.string().min(1).optional(),
  chain: z.string().min(1),
  action: z.string().min(1),
  protocol: z.string().optional(),
  srcAddress: z.string().optional(),
  dstAddress: z.string().optional(),
  srcAddressList: z.string().optional(),
  dstAddressList: z.string().optional(),
  srcPort: z.string().optional(),
  dstPort: z.string().optional(),
  inInterface: z.string().optional(),
  outInterface: z.string().optional(),
  connectionState: z.string().optional(),
  comment: z.string().optional(),
  disabled: z.boolean().optional(),
  log: z.boolean().optional(),
  logPrefix: z.string().optional(),
  tlsHost: z.string().optional(),
  layer7Proto: z.string().optional(),
})

const RulePatchSchema = RuleKeySchema.extend({
  disabled: z.boolean(),
})

const RuleMoveSchema = RuleKeySchema.extend({
  destinationRosId: z.string().min(1).optional(),
})

const AddressKeySchema = z.object({
  serverId: z.string().min(1),
  family: FamilySchema,
  rosId: z.string().min(1),
})

const AddressWriteSchema = z.object({
  serverId: z.string().min(1),
  family: FamilySchema,
  rosId: z.string().min(1).optional(),
  list: z.string().min(1),
  address: z.string().min(1),
  comment: z.string().optional(),
  timeout: z.string().optional(),
  disabled: z.boolean().optional(),
})

const AddressPatchSchema = AddressKeySchema.extend({
  disabled: z.boolean(),
})

function toRosBody(obj: Record<string, string | undefined>): Record<string, string> {
  const out: Record<string, string> = {}
  for (const [k, v] of Object.entries(obj)) {
    if (v !== undefined && v !== "") out[k] = v
  }
  return out
}

function ruleToRos(d: z.infer<typeof RuleWriteSchema>): Record<string, string> {
  return toRosBody({
    chain: d.chain,
    action: d.action,
    protocol: d.protocol && d.protocol !== "all" ? d.protocol : undefined,
    "src-address": d.srcAddress,
    "dst-address": d.dstAddress,
    "src-address-list": d.srcAddressList,
    "dst-address-list": d.dstAddressList,
    "src-port": d.srcPort,
    "dst-port": d.dstPort,
    "in-interface": d.inInterface,
    "out-interface": d.outInterface,
    "connection-state": d.connectionState,
    comment: d.comment,
    disabled: d.disabled === true ? "yes" : d.disabled === false ? "no" : undefined,
    log: d.log === true ? "yes" : d.log === false ? "no" : undefined,
    "log-prefix": d.logPrefix,
    "tls-host": d.tlsHost,
    "layer7-protocol": d.layer7Proto,
  })
}

function addressToRos(d: z.infer<typeof AddressWriteSchema>): Record<string, string> {
  return toRosBody({
    list: d.list,
    address: d.address,
    comment: d.comment,
    timeout: d.timeout,
    disabled: d.disabled === true ? "yes" : d.disabled === false ? "no" : undefined,
  })
}

function rosErr(e: unknown): string {
  if (e instanceof MikrotikError) return e.message
  if (e instanceof Error) return e.message
  return String(e)
}

function requireServer(serverId: string) {
  return getEnabledServerById(serverId)
}

const firewallRoutes: FastifyPluginAsyncZod = async (app) => {
  app.get("/firewall/all", async (_req, reply) => {
    const data = await listFirewallAll()
    return reply.send(data)
  })

  app.post("/firewall/rules", async (req, reply) => {
    const parsed = RuleWriteSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = firewallRestPath(body.family as FirewallFamily, body.table as FirewallTable)
    try {
      await client.put(path, ruleToRos(body))
      return reply.status(201).send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.put("/firewall/rules", async (req, reply) => {
    const parsed = RuleWriteSchema.extend({ rosId: z.string().min(1) }).safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family as FirewallFamily, body.table as FirewallTable)}/${encodeRosId(body.rosId)}`
    try {
      await client.patch(path, ruleToRos(body))
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.patch("/firewall/rules", async (req, reply) => {
    const parsed = RulePatchSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, body.table)}/${encodeRosId(body.rosId)}`
    try {
      await client.patch(path, { disabled: body.disabled ? "yes" : "no" })
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.delete("/firewall/rules", async (req, reply) => {
    const parsed = RuleKeySchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, body.table)}/${encodeRosId(body.rosId)}`
    try {
      await client.delete(path)
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.post("/firewall/rules/move", async (req, reply) => {
    const parsed = RuleMoveSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, body.table)}/move`
    try {
      await client.post(path, {
        numbers: body.rosId,
        ...(body.destinationRosId ? { destination: body.destinationRosId } : {}),
      })
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.post("/firewall/address-lists", async (req, reply) => {
    const parsed = AddressWriteSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    try {
      await client.put(firewallRestPath(body.family, "address-list"), addressToRos(body))
      return reply.status(201).send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.put("/firewall/address-lists", async (req, reply) => {
    const parsed = AddressWriteSchema.extend({ rosId: z.string().min(1) }).safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, "address-list")}/${encodeRosId(body.rosId)}`
    try {
      await client.patch(path, addressToRos(body))
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.patch("/firewall/address-lists", async (req, reply) => {
    const parsed = AddressPatchSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, "address-list")}/${encodeRosId(body.rosId)}`
    try {
      await client.patch(path, { disabled: body.disabled ? "yes" : "no" })
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })

  app.delete("/firewall/address-lists", async (req, reply) => {
    const parsed = AddressKeySchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = requireServer(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const path = `${firewallRestPath(body.family, "address-list")}/${encodeRosId(body.rosId)}`
    try {
      await client.delete(path)
      return reply.send({ ok: true })
    } catch (e) {
      return reply.status(502).send({ error: `RouterOS: ${rosErr(e)}` })
    }
  })
}

export default firewallRoutes
