/**
 * L4 Port Management
 *
 * Generates a Docker Compose override file with the required port mappings
 * for L4 proxy hosts, and manages the apply/status lifecycle via trigger
 * files on a shared volume.
 *
 * Flow:
 * 1. Web app computes required ports from enabled L4 proxy hosts
 * 2. Web writes docker-compose.l4-ports.yml override file
 * 3. Web writes l4-ports.trigger to signal the sidecar
 * 4. Sidecar detects trigger, runs `docker compose up -d caddy`
 * 5. Sidecar writes l4-ports.status with result
 * 6. Web reads status to show user the outcome
 */

import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import crypto from "node:crypto";
import db from "./db";
import { l4ProxyHosts } from "./db/schema";
import { eq } from "drizzle-orm";

const DATA_DIR = process.env.L4_PORTS_DIR || "/app/data";
const OVERRIDE_FILE = "docker-compose.l4-ports.yml";
const TRIGGER_FILE = "l4-ports.trigger";
const STATUS_FILE = "l4-ports.status";

export type L4PortsStatus = {
  state: "idle" | "pending" | "applying" | "applied" | "failed";
  message?: string;
  appliedAt?: string;
  triggeredAt?: string;
  appliedHash?: string;
  error?: string;
};

export type L4PortsDiff = {
  currentPorts: string[];
  requiredPorts: string[];
  needsApply: boolean;
};

/**
 * Compute the set of ports that need to be exposed on the caddy container
 * based on all enabled L4 proxy hosts.
 */
export async function getRequiredL4Ports(): Promise<string[]> {
  const hosts = await db
    .select({
      listenAddress: l4ProxyHosts.listenAddress,
      protocol: l4ProxyHosts.protocol,
    })
    .from(l4ProxyHosts)
    .where(eq(l4ProxyHosts.enabled, true));

  const portSet = new Set<string>();
  for (const host of hosts) {
    const addr = host.listenAddress.trim();
    // Extract port from ":PORT" or "HOST:PORT"
    const match = addr.match(/:(\d+)$/);
    if (!match) continue;
    const port = match[1];
    const proto = host.protocol === "udp" ? "/udp" : "";
    portSet.add(`${port}:${port}${proto}`);
  }

  return Array.from(portSet).sort();
}

/**
 * Read the currently applied ports from the override file on disk.
 */
export function getAppliedL4Ports(): string[] {
  const filePath = join(DATA_DIR, OVERRIDE_FILE);
  if (!existsSync(filePath)) return [];

  try {
    const content = readFileSync(filePath, "utf-8");
    const ports: string[] = [];
    // Simple YAML parsing — extract port lines from "ports:" section
    const lines = content.split("\n");
    let inPorts = false;
    for (const line of lines) {
      if (line.trim() === "ports:") {
        inPorts = true;
        continue;
      }
      if (inPorts) {
        const match = line.match(/^\s+-\s+"(.+)"$/);
        if (match) {
          ports.push(match[1]);
        } else if (line.trim() && !line.startsWith(" ") && !line.startsWith("-")) {
          break; // End of ports section
        }
      }
    }
    return ports.sort();
  } catch {
    return [];
  }
}

/**
 * Compute hash of a port list for change detection.
 */
function hashPorts(ports: string[]): string {
  return crypto.createHash("sha256").update(ports.join(",")).digest("hex").slice(0, 16);
}

/**
 * Check if the current L4 proxy host config differs from applied ports.
 */
export async function getL4PortsDiff(): Promise<L4PortsDiff> {
  const requiredPorts = await getRequiredL4Ports();
  const currentPorts = getAppliedL4Ports();
  const needsApply = hashPorts(requiredPorts) !== hashPorts(currentPorts);
  return { currentPorts, requiredPorts, needsApply };
}

/**
 * Generate the Docker Compose override file and write the trigger.
 * Returns the status after triggering.
 */
export async function applyL4Ports(): Promise<L4PortsStatus> {
  const requiredPorts = await getRequiredL4Ports();

  // Generate the override YAML
  let yaml: string;
  if (requiredPorts.length === 0) {
    // Empty override — no extra ports needed
    yaml = `# Auto-generated by Caddy Proxy Manager — L4 port mappings
# No L4 proxy hosts require additional ports
services: {}
`;
  } else {
    const portLines = requiredPorts.map((p) => `      - "${p}"`).join("\n");
    yaml = `# Auto-generated by Caddy Proxy Manager — L4 port mappings
# Do not edit manually — this file is regenerated when you click "Apply Ports"
services:
  caddy:
    ports:
${portLines}
`;
  }

  const overridePath = join(DATA_DIR, OVERRIDE_FILE);
  const triggerPath = join(DATA_DIR, TRIGGER_FILE);

  // Write the override file
  writeFileSync(overridePath, yaml, "utf-8");

  // Write trigger to signal sidecar
  const triggeredAt = new Date().toISOString();
  writeFileSync(triggerPath, JSON.stringify({
    triggeredAt,
    hash: hashPorts(requiredPorts),
    ports: requiredPorts,
  }), "utf-8");

  return {
    state: "pending",
    message: `Trigger written. Waiting for port manager sidecar to apply ${requiredPorts.length} port(s).`,
    triggeredAt,
  };
}

/**
 * Read the current status from the status file written by the sidecar.
 */
export function getL4PortsStatus(): L4PortsStatus {
  const statusPath = join(DATA_DIR, STATUS_FILE);

  if (!existsSync(statusPath)) {
    return { state: "idle" };
  }

  try {
    return JSON.parse(readFileSync(statusPath, "utf-8")) as L4PortsStatus;
  } catch {
    return { state: "idle" };
  }
}

/**
 * Check if the sidecar container is available by looking for the status file
 * or trigger file having been processed.
 */
export function isSidecarAvailable(): boolean {
  const statusPath = join(DATA_DIR, STATUS_FILE);
  return existsSync(statusPath);
}
