import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { Adapter } from "../adapter";
import { isLoopbackHost, resolveBindHost, type CollectorConfig } from "../config";
import { createAgentSessionsAdapter } from "./agent-sessions";
import { createBotmasterAdapter } from "./botmaster";
import { createClusterAdapter } from "./cluster";
import { createDeliveryAdapter } from "./delivery";
import { createDeployStatusAdapter } from "./deploy-status";
import { createGatesAdapter } from "./gates";
import { createGhciAdapter } from "./ghci";
import { createLandqStateAdapter } from "./landq-state";
import { createFactoryAdapter } from "./factory";
import { createGoliveAdapter } from "./golive";
import { createHarnessAdapter } from "./harness";
import { createOffloadAdapter, requireOffloadToken } from "./offload";
import { createPrometheusAdapter } from "./prometheus";
import { createKubernetesAdapter } from "./kubernetes";
import { createSessionsAdapter } from "./sessions";
import { createSandboxToolgapAdapter } from "./sandbox-toolgap";
import { createSystrayAdapter } from "./systray";
import { createGithubChecksAdapter } from "./github-checks";
import { createSeatsAdapter } from "./seats";
import type { RequestsStore } from "../requests/requests-store";

const SYSTRAY_RUNTIME_DIR = join(homedir(), ".local", "state", "overdeck", "systray", "runtime");
const DEFAULT_SYSTRAY_SNAPSHOT = join(SYSTRAY_RUNTIME_DIR, "health_cache.json");
const DEFAULT_SYSTRAY_CLAUDE_SNAPSHOT = join(SYSTRAY_RUNTIME_DIR, "claude_health_cache.json");
const DEFAULT_CLUSTER_AGENT_CLASS_REGEX = "(claude|cursor-agent|ccr|harnessd|dead-advisor)";
const DEFAULT_GATES_CONFIRMED_PATH = join(
  homedir(),
  "Projects",
  "security-gate",
  "prevent",
  "confirmed.json",
);
const DEFAULT_GATES_PRECISION_PATH = join(
  homedir(),
  "Projects",
  "security-gate",
  "domains",
  "security",
  "recall",
  "precision_records.json",
);
// Enrolled-project roots checked for a delivery receipt (see delivery.ts). Grows as more
// projects adopt the delivery-kind contract (docs/plans/2026-08-15-git-cycle-enrollment.md).
const DEFAULT_DELIVERY_REPOS = [join(homedir(), "Projects", "overdeck")];
const DEFAULT_BOTMASTER_BASE_URL = "http://127.0.0.1:31340";
const DEFAULT_BOTMASTER_TOKEN_PATH = join(homedir(), ".config", "botmaster", "token");
const DEFAULT_OFFLOAD_CONTROLLER_URL = "http://127.0.0.1:8787";
const DEFAULT_OFFLOAD_METRICS_URL = "http://127.0.0.1:8787";
const DEFAULT_FACTORY_DB_PATH = join(homedir(), ".local", "state", "overdeck", "factory", "sssf.db");
const SANDBOX_STATE_DIR = join(homedir(), ".local", "state", "overdeck", "sandbox");
const DEFAULT_SANDBOX_GAPS_PATH = join(SANDBOX_STATE_DIR, "toolgap.jsonl");
const DEFAULT_SANDBOX_IMAGE_TAG_PATH = join(SANDBOX_STATE_DIR, "agent-image-tag");
const DEFAULT_AGENT_SESSIONS_DIR =
  process.env.AGENT_SESSIONS_DIR ?? join(homedir(), ".local", "state", "agent-sessions");

export interface OffloadConnection {
  controllerUrl: string;
  metricsUrl: string;
  token: string;
}

export { requireOffloadToken };

/** Resolve offload controller/metrics URLs from config using the collector's already-loaded token. */
export function resolveOffloadConnection(config: CollectorConfig, token: string): OffloadConnection {
  const settings = config.adapters.offload;
  return {
    controllerUrl: settings?.controllerUrl ?? DEFAULT_OFFLOAD_CONTROLLER_URL,
    metricsUrl: settings?.metricsUrl ?? DEFAULT_OFFLOAD_METRICS_URL,
    token: requireOffloadToken(token),
  };
}

function readBotmasterToken(tokenPath?: string): string {
  const path = tokenPath ?? DEFAULT_BOTMASTER_TOKEN_PATH;
  if (!existsSync(path)) return "";
  return readFileSync(path, "utf8").trim();
}

/**
 * Builds every adapter the config enables. An adapter absent from config.adapters is
 * enabled by default; `enabled = false` omits it. intervalMs overrides the adapter's
 * own default interval.
 */
export function buildAdapters(config: CollectorConfig, token: string, requests?: RequestsStore): Adapter[] {
  const adapters: Adapter[] = [];
  const settings = (id: string) => config.adapters[id];
  const isEnabled = (id: string) => settings(id)?.enabled !== false;
  const intervalOf = (id: string) => settings(id)?.intervalMs;

  const githubChecks = settings("github-checks");
  if (githubChecks?.enabled !== false && githubChecks?.repo && githubChecks.tokenPath && requests) {
    const githubToken = readBotmasterToken(githubChecks.tokenPath);
    if (githubToken) adapters.push(createGithubChecksAdapter({
      repo: githubChecks.repo, token: githubToken, requests,
      interval: intervalOf("github-checks"),
    }));
  }

  if (isEnabled("harness")) {
    adapters.push(
      createHarnessAdapter({
        interval: intervalOf("harness"),
        baseUrl: settings("harness")?.baseUrl,
        requestTimeoutMs: settings("harness")?.requestTimeoutMs,
      }),
    );
  }

  if (isEnabled("systray-ai")) {
    adapters.push(
      createSystrayAdapter({
        interval: intervalOf("systray-ai"),
        snapshotPath: settings("systray-ai")?.snapshotPath ?? DEFAULT_SYSTRAY_SNAPSHOT,
        claudeSnapshotPath: settings("systray-ai")?.claudeSnapshotPath ?? DEFAULT_SYSTRAY_CLAUDE_SNAPSHOT,
      }),
    );
  }

  if (isEnabled("agent-sessions")) {
    adapters.push(
      createAgentSessionsAdapter({
        interval: intervalOf("agent-sessions"),
        dir: DEFAULT_AGENT_SESSIONS_DIR,
        requests,
      }),
    );
  }

  if (isEnabled("ghci")) {
    adapters.push(
      createGhciAdapter({
        interval: intervalOf("ghci"),
        repos: settings("ghci")?.repos,
      }),
    );
  }

  if (isEnabled("prometheus")) {
    adapters.push(
      createPrometheusAdapter({
        interval: intervalOf("prometheus"),
        baseUrl: settings("prometheus")?.baseUrl,
      }),
    );
  }

  if (isEnabled("kubernetes")) {
    adapters.push(createKubernetesAdapter({
      interval: intervalOf("kubernetes"),
      kubeconfigPath: settings("kubernetes")?.kubeconfigPath,
      clusterId: settings("kubernetes")?.clusterId,
      namespaces: settings("kubernetes")?.namespaces,
      prometheusUrl: settings("kubernetes")?.prometheusUrl,
      ledgerDir: settings("sessions")?.ledgerDir,
    }));
  }

  if (isEnabled("golive")) {
    adapters.push(
      createGoliveAdapter({
        interval: intervalOf("golive"),
        repos: settings("golive")?.repos,
      }),
    );
  }

  if (isEnabled("cluster")) {
    adapters.push(
      createClusterAdapter({
        interval: intervalOf("cluster"),
        agentClassRegex:
          settings("cluster")?.agentClassRegex ?? DEFAULT_CLUSTER_AGENT_CLASS_REGEX,
        agentGuardSocketPath: settings("cluster")?.agentGuardSocketPath,
        buildboxStatePath: settings("cluster")?.buildboxStatePath,
        ciFallbackDir: settings("cluster")?.ciFallbackDir,
        buildslotDir: settings("cluster")?.buildslotDir,
        orphanMinEtimeMinutes: settings("cluster")?.orphanMinEtimeMinutes,
        orphanMinCpuPercent: settings("cluster")?.orphanMinCpuPercent,
      }),
    );
  }

  if (settings("deploy-status")?.enabled === true) {
    const deployStatusOffload = resolveOffloadConnection(config, token);
    adapters.push(createDeployStatusAdapter({
      interval: intervalOf("deploy-status"),
      fetchImpl: fetch,
      controllerUrl: deployStatusOffload.controllerUrl,
      controllerToken: deployStatusOffload.token,
    }));
  }

  if (isEnabled("gates")) {
    adapters.push(
      createGatesAdapter({
        interval: intervalOf("gates"),
        confirmedPath: settings("gates")?.confirmedPath ?? DEFAULT_GATES_CONFIRMED_PATH,
        precisionRecordsPath:
          settings("gates")?.precisionRecordsPath ?? DEFAULT_GATES_PRECISION_PATH,
        repos: settings("gates")?.repos ?? [],
        slopgateBaselineRel: settings("gates")?.slopgateBaselineRel,
        warnignorePendingRel: settings("gates")?.warnignorePendingRel,
      }),
    );
  }

  if (isEnabled("botmaster")) {
    adapters.push(
      createBotmasterAdapter({
        interval: intervalOf("botmaster"),
        fetchImpl: fetch,
        baseUrl: settings("botmaster")?.baseUrl ?? DEFAULT_BOTMASTER_BASE_URL,
        token: readBotmasterToken(settings("botmaster")?.tokenPath),
        downThresholdMs: settings("botmaster")?.downThresholdMs,
        slowErrorThreshold: settings("botmaster")?.slowErrorThreshold,
      }),
    );
  }

  if (isEnabled("offload")) {
    const offload = resolveOffloadConnection(config, token);
    adapters.push(
      createOffloadAdapter({
        interval: intervalOf("offload"),
        fetchImpl: fetch,
        controllerUrl: offload.controllerUrl,
        metricsUrl: offload.metricsUrl,
        token: offload.token,
        stallPercent: settings("offload")?.hostStallPercent,
        tmpFullPercent: settings("offload")?.hostTmpFullPercent,
        parityStatePath: settings("offload")?.parityStatePath,
      }),
    );
  }

  if (isEnabled("factory")) {
    const factorySettings = settings("factory");
    adapters.push(
      createFactoryAdapter({
        interval: intervalOf("factory"),
        dbPath:
          factorySettings?.dbPath
          ?? factorySettings?.snapshotPath
          ?? DEFAULT_FACTORY_DB_PATH,
        requests,
      }),
    );
  }

  if (isEnabled("seats")) {
    adapters.push(createSeatsAdapter({
      interval: intervalOf("seats"),
      dir: settings("seats")?.slotDir,
      ledgerDir: settings("sessions")?.ledgerDir,
      requests,
    }));
  }

  if (isEnabled("sessions")) {
    adapters.push(
      createSessionsAdapter({
        interval: intervalOf("sessions"),
        ledgerDir: settings("sessions")?.ledgerDir,
        ...sessionsAttachPosture(config),
      }),
    );
  }

  if (isEnabled("sandbox-toolgap")) {
    adapters.push(
      createSandboxToolgapAdapter({
        interval: intervalOf("sandbox-toolgap"),
        gapsPath: settings("sandbox-toolgap")?.gapsPath ?? DEFAULT_SANDBOX_GAPS_PATH,
        imageTagPath: settings("sandbox-toolgap")?.imageTagPath ?? DEFAULT_SANDBOX_IMAGE_TAG_PATH,
      }),
    );
  }

  if (isEnabled("delivery")) {
    adapters.push(
      createDeliveryAdapter({
        interval: intervalOf("delivery"),
        repos: settings("delivery")?.repos ?? DEFAULT_DELIVERY_REPOS,
      }),
    );
  }

  if (isEnabled("landq-state")) {
    adapters.push(
      createLandqStateAdapter({
        interval: intervalOf("landq-state"),
      }),
    );
  }

  return adapters;
}

/**
 * Browser attach exposes an interactive agent session, so it is gated on the
 * collector's resolved bind being loopback. A tailnet bind deliberately does NOT
 * inherit the attach capability — it is refused and the reason is shown in the UI.
 */
export function sessionsAttachPosture(
  config: CollectorConfig,
): { attachAllowed: boolean; attachBlockedReason?: string } {
  const host = resolveBindHost(config);
  if (isLoopbackHost(host)) return { attachAllowed: true };
  return {
    attachAllowed: false,
    attachBlockedReason: `collector is bound to ${host}, not loopback — browser attach is refused off-host`,
  };
}
