/**
 * RouterOS 7.x REST API client
 *
 * RouterOS 7.1+ exposes a REST API at https://<router>/rest/
 * All responses are JSON; authentication is HTTP Basic or Bearer token.
 *
 * Usage (browser / server-component):
 *   const client = new RouterOsApiClient({ host: "10.0.0.1", port: 443, ... })
 *   const ifaces = await client.getInterfaces()
 */

export interface ApiClientConfig {
  host: string       // "10.0.0.1"
  port: number       // 443 (HTTPS) or 80 (HTTP)
  tls: boolean       // use HTTPS
  username: string
  password: string
  tlsVerify: boolean // verify TLS certificate (false for self-signed)
  timeout: number    // ms, default 8000
}

// ─── RouterOS REST response shapes ───────────────────────────────────────────

export interface RosSystemIdentity {
  name: string
}

export interface RosSystemResource {
  uptime: string
  version: string
  "build-time": string
  "free-memory": string
  "total-memory": string
  "cpu": string
  "cpu-count": string
  "cpu-load": string
  "board-name": string
  "platform": string
  "architecture-name": string
  "bad-blocks": string
}

export interface RosInterface {
  ".id": string
  name: string
  type: string
  mtu: string
  "actual-mtu": string
  "mac-address": string
  running: string
  disabled: string
  comment?: string
}

export interface RosBgpSession {
  ".id": string
  name: string
  "remote.address": string
  "remote.as": string
  state: string
  "uptime": string
  "prefix-count": string
  disabled: string
}

export interface RosWireGuardInterface {
  ".id": string
  name: string
  "listen-port": string
  mtu: string
  "public-key": string
  running: string
  disabled: string
  comment?: string
}

export interface RosWireGuardPeer {
  ".id": string
  interface: string
  "public-key": string
  "endpoint-address"?: string
  "endpoint-port"?: string
  "allowed-address": string
  "last-handshake"?: string
  "rx": string
  "tx": string
  disabled: string
  comment?: string
}

// ─── Error class ─────────────────────────────────────────────────────────────

export class RouterOsApiError extends Error {
  constructor(
    public readonly status: number,
    public readonly detail: string,
    message: string,
  ) {
    super(message)
    this.name = "RouterOsApiError"
  }
}

// ─── Client class ─────────────────────────────────────────────────────────────

export class RouterOsApiClient {
  private readonly baseUrl: string
  private readonly headers: Record<string, string>
  private readonly timeout: number

  constructor(private readonly cfg: ApiClientConfig) {
    const scheme = cfg.tls ? "https" : "http"
    this.baseUrl = `${scheme}://${cfg.host}:${cfg.port}/rest`
    const creds  = btoa(`${cfg.username}:${cfg.password}`)
    this.headers = {
      "Authorization": `Basic ${creds}`,
      "Content-Type":  "application/json",
    }
    this.timeout = cfg.timeout ?? 8000
  }

  // ── low-level fetch ──────────────────────────────────────────────────────

  private async request<T>(
    method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
    path: string,
    body?: unknown,
  ): Promise<T> {
    const controller = new AbortController()
    const timer = setTimeout(() => controller.abort(), this.timeout)

    try {
      const res = await fetch(`${this.baseUrl}${path}`, {
        method,
        headers: this.headers,
        body: body !== undefined ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      })
      clearTimeout(timer)

      if (!res.ok) {
        let detail = ""
        try { detail = (await res.json())?.message ?? "" } catch { /* ignore */ }
        throw new RouterOsApiError(res.status, detail, `RouterOS API ${method} ${path} → ${res.status}: ${detail}`)
      }

      if (res.status === 204) return undefined as T
      return res.json() as Promise<T>
    } catch (e) {
      clearTimeout(timer)
      throw e
    }
  }

  async get<T>(path: string): Promise<T> {
    return this.request<T>("GET", path)
  }

  async post<T>(path: string, body: unknown): Promise<T> {
    return this.request<T>("POST", path, body)
  }

  /** RouterOS uses PUT for updates on a specific item by /.id */
  async put<T>(path: string, id: string, body: unknown): Promise<T> {
    return this.request<T>("PUT", `${path}/${encodeURIComponent(id)}`, body)
  }

  async patch<T>(path: string, id: string, body: unknown): Promise<T> {
    return this.request<T>("PATCH", `${path}/${encodeURIComponent(id)}`, body)
  }

  async delete(path: string, id: string): Promise<void> {
    return this.request<void>("DELETE", `${path}/${encodeURIComponent(id)}`)
  }

  // ── high-level helpers ───────────────────────────────────────────────────

  async getSystemIdentity(): Promise<RosSystemIdentity> {
    return this.get<RosSystemIdentity>("/system/identity")
  }

  async getSystemResource(): Promise<RosSystemResource> {
    return this.get<RosSystemResource>("/system/resource")
  }

  async getInterfaces(): Promise<RosInterface[]> {
    return this.get<RosInterface[]>("/interface")
  }

  async getBgpSessions(): Promise<RosBgpSession[]> {
    return this.get<RosBgpSession[]>("/routing/bgp/session")
  }

  async getWireGuardInterfaces(): Promise<RosWireGuardInterface[]> {
    return this.get<RosWireGuardInterface[]>("/interface/wireguard")
  }

  async getWireGuardPeers(): Promise<RosWireGuardPeer[]> {
    return this.get<RosWireGuardPeer[]>("/interface/wireguard/peers")
  }

  /** Add a WireGuard interface */
  async addWireGuardInterface(params: {
    name: string
    listenPort: number
    mtu?: number
    comment?: string
  }): Promise<RosWireGuardInterface> {
    return this.post<RosWireGuardInterface>("/interface/wireguard", {
      name: params.name,
      "listen-port": String(params.listenPort),
      mtu: String(params.mtu ?? 1420),
      comment: params.comment ?? "",
    })
  }

  /** Add a WireGuard peer */
  async addWireGuardPeer(params: {
    iface: string
    publicKey: string
    allowedAddresses: string[]
    endpoint?: string
    endpointPort?: number
    persistentKeepalive?: number
    comment?: string
  }): Promise<RosWireGuardPeer> {
    return this.post<RosWireGuardPeer>("/interface/wireguard/peers", {
      interface: params.iface,
      "public-key": params.publicKey,
      "allowed-address": params.allowedAddresses.join(","),
      ...(params.endpoint ? { "endpoint-address": params.endpoint } : {}),
      ...(params.endpointPort ? { "endpoint-port": String(params.endpointPort) } : {}),
      ...(params.persistentKeepalive ? { "persistent-keepalive": String(params.persistentKeepalive) } : {}),
      comment: params.comment ?? "",
    })
  }

  /** Ping test from the router — uses /tool/ping */
  async ping(address: string, count = 4): Promise<{ "avg-rtt": string; "packet-loss": string }[]> {
    return this.post("/tool/ping", { address, count: String(count) })
  }
}

// ─── Factory helper ───────────────────────────────────────────────────────────

export function createClient(overrides: Partial<ApiClientConfig> & { host: string }): RouterOsApiClient {
  return new RouterOsApiClient({
    port: 443,
    tls: true,
    username: "admin",
    password: "",
    tlsVerify: false,
    timeout: 8000,
    ...overrides,
  })
}
