import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import {
  MikrotikClient,
  firewallRestPath,
} from "./mikrotik.js"
import type {
  FirewallFamily,
  FirewallTable,
  RosFirewallAddressList,
  RosFirewallFilter,
} from "../types/server.js"

type ServerRow = typeof servers.$inferSelect

export interface FirewallRuleDto {
  id: string
  rosId: string
  serverId: string
  serverName: string
  family: FirewallFamily
  table: FirewallTable
  chain: string
  action: string
  proto: string
  src: string
  dst: string
  port: string
  iface: string
  comment: string
  enabled: boolean
  hits: number
  log: boolean
  logPrefix: string
  tlsHost?: string
  layer7Proto?: string
}

export interface FirewallAddressListDto {
  id: string
  rosId: string
  serverId: string
  serverName: string
  family: FirewallFamily
  list: string
  address: string
  comment: string
  disabled: boolean
  timeout?: string
}

const TABLES: FirewallTable[] = ["filter", "nat", "mangle", "raw"]
const FAMILIES: FirewallFamily[] = ["ip", "ip6"]

function dash(v: string | undefined): string {
  const s = v?.trim() ?? ""
  return s.length > 0 ? s : "—"
}

function rosDisabled(v: string | undefined): boolean {
  return v === "true" || v === "yes"
}

function parseHits(raw: RosFirewallFilter): number {
  const n = Number.parseInt(raw.packets ?? "0", 10)
  return Number.isFinite(n) ? n : 0
}

export function ruleUiId(
  serverId: string | number,
  family: FirewallFamily,
  table: FirewallTable,
  rosId: string,
): string {
  return `${serverId}:${family}:${table}:${rosId}`
}

export function addressUiId(
  serverId: string | number,
  family: FirewallFamily,
  rosId: string,
): string {
  return `${serverId}:${family}:address-list:${rosId}`
}

export function mapFirewallRule(
  server: ServerRow,
  family: FirewallFamily,
  table: FirewallTable,
  raw: RosFirewallFilter,
  idx: number,
): FirewallRuleDto {
  const rosId = raw[".id"] || `*${idx}`
  const src = raw["src-address"] || raw["src-address-list"]
  const dst = raw["dst-address"] || raw["dst-address-list"]
  const port = raw["dst-port"] || raw["src-port"]
  const iface = raw["in-interface"] || raw["out-interface"]
  return {
    id: ruleUiId(server.id, family, table, rosId),
    rosId,
    serverId: String(server.id),
    serverName: server.name || server.host,
    family,
    table,
    chain: raw.chain || "",
    action: raw.action || "",
    proto: raw.protocol || "all",
    src: dash(src),
    dst: dash(dst),
    port: dash(port),
    iface: dash(iface),
    comment: raw.comment ?? "",
    enabled: !rosDisabled(raw.disabled),
    hits: parseHits(raw),
    log: raw.log === "true" || raw.log === "yes",
    logPrefix: raw["log-prefix"] ?? "",
    tlsHost: raw["tls-host"],
    layer7Proto: raw["layer7-protocol"],
  }
}

export function mapAddressList(
  server: ServerRow,
  family: FirewallFamily,
  raw: RosFirewallAddressList,
  idx: number,
): FirewallAddressListDto {
  const rosId = raw[".id"] || `*${idx}`
  return {
    id: addressUiId(server.id, family, rosId),
    rosId,
    serverId: String(server.id),
    serverName: server.name || server.host,
    family,
    list: raw.list || "",
    address: raw.address || "",
    comment: raw.comment ?? "",
    disabled: rosDisabled(raw.disabled),
    timeout: raw.timeout,
  }
}

async function safeGet<T>(fn: () => Promise<T[]>, fallback: T[] = []): Promise<T[]> {
  try {
    const rows = await fn()
    return Array.isArray(rows) ? rows : fallback
  } catch {
    return fallback
  }
}

export async function fetchServerFirewall(server: ServerRow): Promise<{
  rules: FirewallRuleDto[]
  addressLists: FirewallAddressListDto[]
}> {
  const client = MikrotikClient.fromServer(server)
  const ruleJobs = FAMILIES.flatMap((family) =>
    TABLES.map(async (table) => {
      const raw = await safeGet(() => client.getFirewallRules(family, table))
      return raw.map((row, idx) => mapFirewallRule(server, family, table, row, idx))
    }),
  )
  const listJobs = FAMILIES.map(async (family) => {
    const raw = await safeGet(() => client.getFirewallAddressList(family))
    return raw.map((row, idx) => mapAddressList(server, family, row, idx))
  })
  const [ruleChunks, listChunks] = await Promise.all([
    Promise.all(ruleJobs),
    Promise.all(listJobs),
  ])
  return {
    rules: ruleChunks.flat(),
    addressLists: listChunks.flat(),
  }
}

export async function listFirewallAll(): Promise<{
  rules: FirewallRuleDto[]
  addressLists: FirewallAddressListDto[]
}> {
  const allServers = db.select().from(servers).where(eq(servers.enabled, true)).all()
  const perServer = await Promise.all(
    allServers.map(async (server) => {
      try {
        return await fetchServerFirewall(server)
      } catch {
        return { rules: [] as FirewallRuleDto[], addressLists: [] as FirewallAddressListDto[] }
      }
    }),
  )
  return {
    rules: perServer.flatMap((r) => r.rules),
    addressLists: perServer.flatMap((r) => r.addressLists),
  }
}

export { firewallRestPath, FAMILIES, TABLES }
