import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import {
  wgCreateInterfaceSchema,
  wgCreatePeerRequestSchema,
  wgExportRequestSchema,
  wgImportRequestSchema,
  wgPatchInterfaceSchema,
  wgPatchPeerSchema,
  type WgCreatePeerRequest,
  type WgIfaceDto,
} from "@mmapp/contracts/wireguard"
import { MikrotikClient, MikrotikError } from "../services/mikrotik.js"
import {
  generateMikrotikRsc,
  generateNativeConf,
  generatePeerClientConf,
  parseWgConfig,
  type WgParsedConfig,
} from "../services/wireguard-config.js"
import {
  getEnabledServerById,
  listWireGuardInterfaces,
} from "../services/wireguard-live.js"
import {
  putIpAddress,
  putWireguardInterface,
  putWireguardPeer,
  toRosBody,
} from "../services/wireguard-ros.js"

function serverIdParam(v: string): string {
  return decodeURIComponent(v)
}

function rosIdParam(v: string): string {
  return decodeURIComponent(v)
}

function peerToRosBody(p: Omit<WgCreatePeerRequest, "serverId" | "interfaceName"> & { interfaceName: string }) {
  return toRosBody({
    interface: p.interfaceName,
    "public-key": p.publicKey,
    "allowed-address": p.allowedAddresses.join(","),
    "endpoint-address": p.endpointAddress,
    "endpoint-port": p.endpointPort != null ? String(p.endpointPort) : undefined,
    "persistent-keepalive":
      p.persistentKeepalive != null ? String(p.persistentKeepalive) : undefined,
    comment: p.comment,
    name: p.name,
    "private-key": typeof p.privateKey === "string" ? p.privateKey : undefined,
    "client-address": p.clientAddress,
    "client-dns": p.clientDns,
    "client-endpoint": p.clientEndpoint,
    disabled: p.disabled === true ? "yes" : p.disabled === false ? "no" : undefined,
  })
}

function previewFromParsed(parsed: WgParsedConfig) {
  return {
    format: parsed.format,
    interface: {
      name: parsed.interface.name,
      listenPort: parsed.interface.listenPort,
      mtu: parsed.interface.mtu,
      privateKey: parsed.interface.privateKey,
      comment: parsed.interface.comment,
      address: parsed.interface.address,
      disabled: parsed.interface.disabled,
    },
    peers: parsed.peers.map((p) => ({
      publicKey: p.publicKey,
      allowedAddresses: p.allowedAddresses,
      endpointAddress: p.endpointAddress,
      endpointPort: p.endpointPort,
      persistentKeepalive: p.persistentKeepalive,
      comment: p.comment,
      name: p.name,
      privateKey: p.privateKey,
      clientAddress: p.clientAddress,
      clientDns: p.clientDns,
      clientEndpoint: p.clientEndpoint,
      disabled: p.disabled,
    })),
  }
}

async function applyParsedConfig(
  client: MikrotikClient,
  parsed: WgParsedConfig,
): Promise<{ interfaceName: string; peersCreated: number }> {
  const name = parsed.interface.name
  const ifaceBody = toRosBody({
    name,
    "listen-port": String(parsed.interface.listenPort ?? 13231),
    mtu: String(parsed.interface.mtu ?? 1420),
    "private-key": parsed.interface.privateKey,
    comment: parsed.interface.comment,
    disabled: parsed.interface.disabled ? "yes" : undefined,
  })
  await putWireguardInterface(client, ifaceBody)

  if (parsed.interface.address) {
    await putIpAddress(client, parsed.interface.address, name)
  }

  let peersCreated = 0
  for (const p of parsed.peers) {
    if (!p.publicKey) continue
    await putWireguardPeer(
      client,
      peerToRosBody({
        interfaceName: name,
        publicKey: p.publicKey,
        allowedAddresses: p.allowedAddresses.length ? p.allowedAddresses : ["0.0.0.0/0"],
        endpointAddress: p.endpointAddress,
        endpointPort: p.endpointPort,
        persistentKeepalive: p.persistentKeepalive,
        comment: p.comment,
        name: p.name,
        privateKey: p.privateKey,
        clientAddress: p.clientAddress,
        clientDns: p.clientDns,
        clientEndpoint: p.clientEndpoint,
        disabled: p.disabled,
      }),
    )
    peersCreated += 1
  }
  return { interfaceName: name, peersCreated }
}

function findIface(
  list: WgIfaceDto[],
  serverId: string,
  interfaceName: string,
): WgIfaceDto | undefined {
  return list.find((i) => i.serverId === serverId && i.name === interfaceName)
}

const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
  app.get("/wireguard", async (req, reply) => {
    const q = req.query as { serverId?: string; includePrivateKey?: string }
    const includePrivateKey = q.includePrivateKey === "1" || q.includePrivateKey === "true"
    const result = await listWireGuardInterfaces({
      serverId: q.serverId,
      includePrivateKey,
    })
    return reply.send(result)
  })

  app.post("/wireguard/interfaces", async (req, reply) => {
    const parsed = wgCreateInterfaceSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = getEnabledServerById(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })

    const client = MikrotikClient.fromServer(server)
    try {
      await putWireguardInterface(client, {
        name: body.name,
        "listen-port": String(body.listenPort),
        mtu: String(body.mtu),
        comment: body.comment,
        "private-key": body.privateKey,
        disabled: body.disabled ? "yes" : undefined,
      })

      if (body.address) {
        await putIpAddress(client, body.address, body.name)
      }

      if (body.peer) {
        await putWireguardPeer(client, peerToRosBody({ ...body.peer, interfaceName: body.name }))
      }

      const list = await listWireGuardInterfaces({
        serverId: String(server.id),
        includePrivateKey: true,
      })
      const created = list.interfaces.find((i) => i.name === body.name)
      return reply.status(201).send(created ?? { ok: true, name: body.name })
    } catch (e) {
      const msg = e instanceof MikrotikError ? e.message : e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.patch("/wireguard/interfaces/:serverId/:rosId", async (req, reply) => {
    const { serverId, rosId } = req.params as { serverId: string; rosId: string }
    const parsed = wgPatchInterfaceSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const server = getEnabledServerById(serverIdParam(serverId))
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    const d = parsed.data
    try {
      await client.patch(
        `/interface/wireguard/${encodeURIComponent(rosIdParam(rosId))}`,
        toRosBody({
          name: d.name,
          "listen-port": d.listenPort != null ? String(d.listenPort) : undefined,
          mtu: d.mtu != null ? String(d.mtu) : undefined,
          comment: d.comment,
          "private-key": d.privateKey,
          disabled: d.disabled === true ? "yes" : d.disabled === false ? "no" : undefined,
        }),
      )
      return reply.send({ ok: true })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.delete("/wireguard/interfaces/:serverId/:rosId", async (req, reply) => {
    const { serverId, rosId } = req.params as { serverId: string; rosId: string }
    const server = getEnabledServerById(serverIdParam(serverId))
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    try {
      await client.delete(`/interface/wireguard/${encodeURIComponent(rosIdParam(rosId))}`)
      return reply.send({ ok: true })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.post("/wireguard/peers", async (req, reply) => {
    const parsed = wgCreatePeerRequestSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = getEnabledServerById(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    try {
      await putWireguardPeer(client, peerToRosBody(body))
      return reply.status(201).send({ ok: true })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.patch("/wireguard/peers/:serverId/:rosId", async (req, reply) => {
    const { serverId, rosId } = req.params as { serverId: string; rosId: string }
    const parsed = wgPatchPeerSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const server = getEnabledServerById(serverIdParam(serverId))
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const d = parsed.data
    const client = MikrotikClient.fromServer(server)
    try {
      await client.patch(
        `/interface/wireguard/peers/${encodeURIComponent(rosIdParam(rosId))}`,
        toRosBody({
          "public-key": d.publicKey,
          "allowed-address": d.allowedAddresses?.join(","),
          "endpoint-address": d.endpointAddress,
          "endpoint-port": d.endpointPort != null ? String(d.endpointPort) : undefined,
          "persistent-keepalive":
            d.persistentKeepalive != null ? String(d.persistentKeepalive) : undefined,
          comment: d.comment,
          name: d.name,
          "client-address": d.clientAddress,
          "client-dns": d.clientDns,
          "client-endpoint": d.clientEndpoint,
          disabled: d.disabled === true ? "yes" : d.disabled === false ? "no" : undefined,
        }),
      )
      return reply.send({ ok: true })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.delete("/wireguard/peers/:serverId/:rosId", async (req, reply) => {
    const { serverId, rosId } = req.params as { serverId: string; rosId: string }
    const server = getEnabledServerById(serverIdParam(serverId))
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    try {
      await client.delete(`/interface/wireguard/peers/${encodeURIComponent(rosIdParam(rosId))}`)
      return reply.send({ ok: true })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}` })
    }
  })

  app.post("/wireguard/import", async (req, reply) => {
    const parsed = wgImportRequestSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    let config: WgParsedConfig
    try {
      config = parseWgConfig(body.content, body.format)
    } catch (e) {
      return reply.status(400).send({ error: e instanceof Error ? e.message : "Ошибка разбора конфига" })
    }
    const preview = previewFromParsed(config)
    if (body.dryRun) {
      return reply.send({ dryRun: true, preview })
    }

    const server = getEnabledServerById(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })
    const client = MikrotikClient.fromServer(server)
    try {
      const applied = await applyParsedConfig(client, config)
      return reply.send({ dryRun: false, preview, applied })
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return reply.status(502).send({ error: `RouterOS: ${msg}`, preview })
    }
  })

  app.post("/wireguard/export", async (req, reply) => {
    const parsed = wgExportRequestSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const body = parsed.data
    const server = getEnabledServerById(body.serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })

    const list = await listWireGuardInterfaces({
      serverId: String(server.id),
      includePrivateKey: body.includePrivateKey === true,
    })
    const iface = findIface(list.interfaces, String(server.id), body.interfaceName)
    if (!iface) return reply.status(404).send({ error: "Интерфейс не найден" })

    if (body.format === "rsc") {
      const content = generateMikrotikRsc({
        name: iface.name,
        listenPort: iface.listenPort,
        mtu: iface.mtu,
        comment: iface.comment,
        enabled: iface.enabled,
        privateKey: body.includePrivateKey ? iface.privateKey : undefined,
        publicKey: iface.publicKey,
        address: iface.address,
        serverName: iface.serverName,
        peers: iface.peers.map((p) => ({
          publicKey: p.publicKey,
          allowedIps: p.allowedIps,
          endpoint: p.endpoint,
          persistentKeepalive: p.persistentKeepalive,
          persistent: p.persistent,
          comment: p.comment,
          name: p.name,
          clientAddress: p.clientAddress,
          clientDns: p.clientDns,
          clientEndpoint: p.clientEndpoint,
        })),
      })
      return reply.send({
        format: "rsc",
        filename: `${iface.name}.rsc`,
        content,
      })
    }

    if (body.format === "conf") {
      const content = generateNativeConf(
        {
          name: iface.name,
          listenPort: iface.listenPort,
          mtu: iface.mtu,
          comment: iface.comment,
          enabled: iface.enabled,
          privateKey: iface.privateKey,
          publicKey: iface.publicKey,
          address: iface.address,
          serverName: iface.serverName,
          peers: iface.peers.map((p) => ({
            publicKey: p.publicKey,
            allowedIps: p.allowedIps,
            endpoint: p.endpoint,
            persistentKeepalive: p.persistentKeepalive,
            persistent: p.persistent,
            comment: p.comment,
          })),
        },
        { includePrivateKey: body.includePrivateKey === true },
      )
      return reply.send({
        format: "conf",
        filename: `${iface.name}.conf`,
        content,
      })
    }

    // peer-conf
    const peer = body.peerId
      ? iface.peers.find((p) => p.id === body.peerId || p.rosId === body.peerId)
      : iface.peers[0]
    if (!peer) return reply.status(404).send({ error: "Пир не найден" })
    if (!iface.publicKey) {
      return reply.status(400).send({ error: "У интерфейса нет public-key" })
    }
    const endpoint =
      peer.clientEndpoint ||
      (peer.endpoint
        ? peer.endpoint
        : undefined)
    const content = generatePeerClientConf({
      peerAddress: peer.clientAddress,
      peerDns: peer.clientDns,
      serverPublicKey: iface.publicKey,
      allowedIps: peer.allowedIps.length ? peer.allowedIps : ["0.0.0.0/0"],
      endpoint:
        endpoint ||
        (peer.clientEndpoint
          ? peer.clientEndpoint.includes(":")
            ? peer.clientEndpoint
            : `${peer.clientEndpoint}:${iface.listenPort}`
          : undefined),
      persistentKeepalive: peer.persistentKeepalive ?? 25,
    })
    return reply.send({
      format: "peer-conf",
      filename: `${iface.name}-peer.conf`,
      content,
    })
  })
}

export default wireguardRoutes
