import { prisma } from "../db";
import { getStrategy, resolveRef, VERSION_PROBE, type DeployMethod } from "../deploy";
import { hermesRunner } from "./hermes";
import { openclawRunner } from "./openclaw";
import { opencodeRunner } from "./opencode";
import type { AgentKind, AgentRunner, AgentRunnerCtx } from "./types";

export * from "./types";

const RUNNERS: Record<AgentKind, AgentRunner> = {
  opencode: opencodeRunner,
  openclaw: openclawRunner,
  hermes: hermesRunner,
};

export function getRunner(kind: string): AgentRunner {
  const r = RUNNERS[kind as AgentKind];
  if (!r) throw new Error(`unknown_agent_kind:${kind}`);
  return r;
}

export type ActionName =
  | "install"
  | "update"
  | "status"
  | "start"
  | "stop"
  | "restart"
  | "logs"
  | "uninstall";

type AgentRow = {
  id: string;
  hostId: string;
  name: string;
  kind: AgentKind;
  installPath: string;
  configJson: string;
  deployMethod: DeployMethod;
  deployRef: string;
  declared: boolean;
};

async function loadCtx(agentId: string): Promise<AgentRow> {
  const a = await prisma.agent.findUnique({ where: { id: agentId } });
  if (!a) throw new Error(`agent_not_found:${agentId}`);
  return {
    id: a.id,
    hostId: a.hostId,
    name: a.name,
    kind: a.kind as AgentKind,
    installPath: a.installPath,
    configJson: a.configJson,
    deployMethod: a.deployMethod as DeployMethod,
    deployRef: a.deployRef,
    declared: a.declared,
  };
}

export async function runAction(
  agentId: string,
  action: ActionName,
): Promise<{ ok: boolean; log: string; version?: string; running?: boolean; pid?: number; uptimeSec?: number; restartCount?: number }> {
  const ctx = await loadCtx(agentId);
  const start = Date.now();
  let result: { ok: boolean; log: string; version?: string; running?: boolean; pid?: number; uptimeSec?: number; restartCount?: number };

  try {
    if (action === "install" || action === "update") {
      // Managed lifecycle — chỉ áp dụng khi không phải declared và deployMethod=systemd.
      if (ctx.declared) {
        result = { ok: false, log: "Agent đã được khai báo (deploy ngoài AM). Không thể install/update qua AM." };
      } else if (ctx.deployMethod !== "systemd") {
        result = { ok: false, log: `Install/update tự động chỉ hỗ trợ deploy method = systemd (hiện tại: ${ctx.deployMethod}).` };
      } else {
        const runner = getRunner(ctx.kind);
        const runnerCtx: AgentRunnerCtx = {
          agentId: ctx.id,
          hostId: ctx.hostId,
          name: ctx.name,
          installPath: ctx.installPath,
          configJson: ctx.configJson,
        };
        result = action === "install" ? await runner.install(runnerCtx) : await runner.update(runnerCtx);
      }
    } else {
      // status / start / stop / logs / uninstall — đi qua strategy.
      const strategy = getStrategy(ctx.deployMethod);
      const ref = resolveRef(ctx.deployMethod, ctx.deployRef, ctx.name);
      if (!ref) {
        result = { ok: false, log: `deployRef trống — phương thức ${ctx.deployMethod} cần khai báo tên unit/container/process.` };
      } else {
        const sCtx = { hostId: ctx.hostId, ref, versionProbe: VERSION_PROBE[ctx.kind] };
        switch (action) {
          case "status": result = await strategy.status(sCtx); break;
          case "start": result = await strategy.start(sCtx); break;
          case "stop": result = await strategy.stop(sCtx); break;
          case "restart": result = await strategy.restart(sCtx); break;
          case "logs": result = await strategy.logs(sCtx); break;
          case "uninstall":
            if (ctx.declared) {
              result = { ok: false, log: "Agent đã khai báo — không thể uninstall qua AM. Xoá row hoặc gỡ thủ công trên host." };
            } else {
              result = await strategy.uninstall(sCtx);
            }
            break;
          default: throw new Error(`unknown_action:${action}`);
        }
      }
    }
  } catch (e: unknown) {
    const msg = e instanceof Error ? e.message : String(e);
    result = { ok: false, log: `ERROR: ${msg}` };
  }

  // Cập nhật trạng thái agent dựa trên kết quả.
  const update: Record<string, unknown> = { updatedAt: new Date() };
  if (action === "install" && result.ok) update.status = "stopped";
  if (action === "uninstall" && result.ok) update.status = "unknown";
  if (action === "start" && result.ok) update.status = "running";
  if (action === "restart" && result.ok) update.status = "running";
  if (action === "stop" && result.ok) update.status = "stopped";
  if (action === "status") {
    update.status = result.running ? "running" : "stopped";
    update.lastSeenAt = new Date();
    if (result.pid) update.pid = result.pid;
    else update.pid = null;
    // Clamp về Int32 (Prisma SQLite Int max ~2.1B). Một số probe trả epoch ms vào nhầm chỗ.
    const clamp = (v: number | undefined, max: number) =>
      v === undefined ? null : Math.max(0, Math.min(max, Math.floor(v)));
    update.uptimeSec = clamp(result.uptimeSec, 2_000_000_000); // ~63 năm
    if (result.restartCount !== undefined) {
      update.restartCount = clamp(result.restartCount, 1_000_000); // 1 triệu lần restart đủ
    }
  }
  if (result.version) update.version = result.version;
  if (!result.ok && (action === "install" || action === "update" || action === "start")) {
    update.status = "error";
  }
  await prisma.agent.update({ where: { id: agentId }, data: update });

  await prisma.agentEvent.create({
    data: {
      agentId,
      kind: action,
      status: result.ok ? "ok" : "fail",
      message: result.ok ? "ok" : "failed",
      log: result.log.slice(0, 64_000),
      durationMs: Date.now() - start,
    },
  });

  return result;
}
