import fs from "fs";
import os from "os";
import path from "path";
import { spawnSync } from "child_process";

const DEFAULT_TIMEOUT_MS = 3000;
const VALID_KINDS = new Set(["http", "process", "pm2", "docker", "hook"]);

export function getWatchdogDir() {
  if (process.env.CLAUTH_WATCHDOG_DIR) return process.env.CLAUTH_WATCHDOG_DIR;
  const appdata = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
  return path.join(appdata, "clauth");
}

export function getRegistryPath() {
  return path.join(getWatchdogDir(), "watchdog-services.json");
}

export function getEventsPath() {
  return path.join(getWatchdogDir(), "watchdog-events.jsonl");
}

function readJsonFile(filePath, fallback) {
  try {
    if (!fs.existsSync(filePath)) return fallback;
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch {
    return fallback;
  }
}

function writeJsonFile(filePath, value) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}

function appendEvent(event) {
  fs.mkdirSync(getWatchdogDir(), { recursive: true });
  fs.appendFileSync(getEventsPath(), `${JSON.stringify({ ts: new Date().toISOString(), ...event })}\n`, "utf8");
}

function normalizeCommand(command) {
  if (!command) return null;
  if (Array.isArray(command)) {
    const [cmd, ...args] = command;
    return { cmd, args: args.map(String) };
  }
  if (typeof command === "object" && typeof command.cmd === "string") {
    return { cmd: command.cmd, args: Array.isArray(command.args) ? command.args.map(String) : [] };
  }
  return null;
}

function validateCommand(command, field) {
  const normalized = normalizeCommand(command);
  if (!normalized) return null;
  const cmd = normalized.cmd.trim();
  if (!cmd) throw new Error(`${field}.cmd is required`);
  if (/[;&|<>]/.test(cmd)) throw new Error(`${field}.cmd must be an executable path/name, not shell syntax`);
  for (const arg of normalized.args) {
    if (/[<>]/.test(arg)) throw new Error(`${field}.args contains unsupported shell redirection`);
  }
  return normalized;
}

export function validateWatchdogService(service) {
  if (!service || typeof service !== "object") throw new Error("service must be an object");
  if (!service.id || typeof service.id !== "string") throw new Error("service.id is required");
  if (!/^[a-zA-Z0-9_.-]+$/.test(service.id)) throw new Error("service.id may contain only letters, numbers, dot, underscore, and dash");
  if (!service.label || typeof service.label !== "string") throw new Error("service.label is required");
  if (!VALID_KINDS.has(service.kind)) throw new Error(`service.kind must be one of ${[...VALID_KINDS].join(", ")}`);

  const restart = validateCommand(service.restart, "service.restart");
  const start = validateCommand(service.start, "service.start");
  const health = service.health && typeof service.health === "object" ? service.health : null;
  if (health?.url && typeof health.url !== "string") throw new Error("service.health.url must be a string");
  if (health?.url && !/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(?::\d+)?\//.test(health.url)) {
    throw new Error("service.health.url must be localhost-only");
  }

  return {
    id: service.id,
    label: service.label,
    owner: service.owner || "local",
    kind: service.kind,
    health,
    start,
    restart,
    logs: Array.isArray(service.logs) ? service.logs.map(String) : [],
    tags: Array.isArray(service.tags) ? service.tags.map(String) : [],
    approvalRequired: service.approvalRequired !== false,
    restartPolicy: service.restartPolicy || "manual",
  };
}

export function validateWatchdogManifest(manifest) {
  if (!manifest || typeof manifest !== "object") throw new Error("manifest must be an object");
  const services = Array.isArray(manifest.services) ? manifest.services : null;
  if (!services || services.length === 0) throw new Error("manifest.services must be a non-empty array");
  return {
    schema: manifest.schema || "clauth.watchdog.v1",
    source: manifest.source || "manual",
    services: services.map(validateWatchdogService),
  };
}

export function loadRegistry() {
  const registry = readJsonFile(getRegistryPath(), { services: [] });
  return {
    services: Array.isArray(registry.services) ? registry.services.map(validateWatchdogService) : [],
  };
}

export function saveRegistry(registry) {
  writeJsonFile(getRegistryPath(), { services: registry.services.map(validateWatchdogService) });
}

export function registerWatchdogManifest(manifest) {
  const validated = validateWatchdogManifest(manifest);
  const registry = loadRegistry();
  const byId = new Map(registry.services.map((service) => [service.id, service]));
  for (const service of validated.services) byId.set(service.id, service);
  const next = { services: [...byId.values()].sort((a, b) => a.id.localeCompare(b.id)) };
  saveRegistry(next);
  appendEvent({ kind: "register", source: validated.source, service_count: validated.services.length });
  return { registered: validated.services.length, services: validated.services.map((service) => service.id) };
}

export async function evaluateWatchdogService(service) {
  const checkedAt = new Date().toISOString();
  if (service.health?.url) {
    const timeoutMs = Number(service.health.timeoutMs || DEFAULT_TIMEOUT_MS);
    try {
      const response = await fetch(service.health.url, { signal: AbortSignal.timeout(timeoutMs), cache: "no-store" });
      return {
        ...service,
        status: response.ok ? "healthy" : "degraded",
        checkedAt,
        httpStatus: response.status,
      };
    } catch (error) {
      return {
        ...service,
        status: "unreachable",
        checkedAt,
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }
  return { ...service, status: "unknown", checkedAt };
}

export async function getWatchdogStatuses() {
  const registry = loadRegistry();
  const services = await Promise.all(registry.services.map(evaluateWatchdogService));
  return {
    checkedAt: new Date().toISOString(),
    total: services.length,
    healthy: services.filter((service) => service.status === "healthy").length,
    degraded: services.filter((service) => service.status === "degraded").length,
    unreachable: services.filter((service) => service.status === "unreachable").length,
    services,
  };
}

export function readWatchdogEvents(limit = 100) {
  try {
    if (!fs.existsSync(getEventsPath())) return [];
    return fs.readFileSync(getEventsPath(), "utf8")
      .split(/\r?\n/)
      .filter(Boolean)
      .slice(-limit)
      .map((line) => {
        try { return JSON.parse(line); } catch { return { raw: line }; }
      });
  } catch {
    return [];
  }
}

export function restartWatchdogService(id) {
  const service = loadRegistry().services.find((candidate) => candidate.id === id);
  if (!service) return { ok: false, error: "service_not_registered" };
  if (!service.restart) return { ok: false, error: "restart_not_configured" };
  if (service.approvalRequired && process.env.CLAUTH_WATCHDOG_APPROVED !== "1") {
    return { ok: false, error: "approval_required" };
  }

  const result = spawnSync(service.restart.cmd, service.restart.args, {
    cwd: service.restart.cwd || process.cwd(),
    windowsHide: true,
    encoding: "utf8",
    timeout: Number(service.restart.timeoutMs || 30000),
  });
  const event = {
    kind: "restart",
    service_id: id,
    status: result.status,
    error: result.error ? result.error.message : undefined,
  };
  appendEvent(event);
  return {
    ok: result.status === 0,
    status: result.status,
    stdout: result.stdout,
    stderr: result.stderr,
    error: result.error ? result.error.message : undefined,
  };
}
