import { createMeshHub } from "./hub.ts";
import { createWriteStream, mkdirSync, readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { resolveConfiguredHubProjectTokens, selectHubProjectTokenMap } from "./hub-identity.ts";
import {
  DEFAULT_MESSAGE_RETENTION_MS,
  DEFAULT_MESSAGE_TTL_MS,
  DEFAULT_PORT,
  DEFAULT_RATE_LIMIT_MAX,
  DEFAULT_RATE_LIMIT_WINDOW_MS,
} from "./protocol.ts";
import { parseWorkflowDefinitions } from "./workflow.ts";

const host = process.env.KXM_HOST ?? "127.0.0.1";
const port = Number.parseInt(process.env.KXM_PORT ?? String(DEFAULT_PORT), 10);
const authToken = process.env.KXM_AUTH_TOKEN;
const workspaceDir = resolve(process.env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
const configDir = resolve(process.env.KXM_CONFIG_DIR?.trim() || join(workspaceDir, "config"));
const logsDir = resolve(process.env.KXM_LOGS_DIR?.trim() || join(workspaceDir, "logs"));
const assetsDir = resolve(process.env.KXM_ASSETS_DIR?.trim() || join(workspaceDir, "assets"));
const stateDir = resolve(process.env.KXM_STATE_DIR?.trim() || join(workspaceDir, "state"));
const dataPathValue = process.env.KXM_DATA_PATH?.trim();
const dataPath = dataPathValue === ":memory:" ? dataPathValue : resolve(dataPathValue || join(stateDir, "kxm.db"));
const logPath = resolve(process.env.KXM_LOG_PATH?.trim() || join(logsDir, "kxm-hub.jsonl"));
const messageTtlMs = Number.parseInt(process.env.KXM_MESSAGE_TTL_MS ?? String(DEFAULT_MESSAGE_TTL_MS), 10);
const messageRetentionMs = Number.parseInt(
  process.env.KXM_MESSAGE_RETENTION_MS ?? String(DEFAULT_MESSAGE_RETENTION_MS),
  10,
);
const rateLimitMax = Number.parseInt(process.env.KXM_RATE_LIMIT_MAX ?? String(DEFAULT_RATE_LIMIT_MAX), 10);
import { createLogger } from "./logger.ts";

const rateLimitWindowMs = Number.parseInt(
  process.env.KXM_RATE_LIMIT_WINDOW_MS ?? String(DEFAULT_RATE_LIMIT_WINDOW_MS),
  10,
);

for (const directory of [configDir, logsDir, assetsDir, stateDir, dirname(logPath)]) {
  mkdirSync(directory, { recursive: true });
}
const structuredLog = createLogger({
  component: "hub",
  path: logPath,
});

function parseProjectTokenEnv(raw: string | undefined): Record<string, string> | undefined {
  const trimmed = raw?.trim();
  if (!trimmed) return undefined;
  const value = JSON.parse(trimmed) as unknown;
  if (!value || Array.isArray(value) || typeof value !== "object") {
    throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
  }
  const entries = Object.entries(value as Record<string, unknown>);
  if (entries.some(([project, token]) => !project.trim() || typeof token !== "string" || !token.trim())) {
    throw new Error("KXM_PROJECT_TOKENS must contain non-empty project names and token strings");
  }
  return Object.fromEntries(entries) as Record<string, string>;
}

function projectTokens(): Record<string, string> | undefined {
  const explicit = process.env.KXM_PROJECT_TOKENS_EXPLICIT === "1";
  const fromEnv = parseProjectTokenEnv(process.env.KXM_PROJECT_TOKENS);
  const workdir = resolve(process.env.KXM_WORKDIR?.trim() || process.cwd());
  const configured = explicit ? undefined : resolveConfiguredHubProjectTokens(workdir, process.env);
  const selected = selectHubProjectTokenMap({
    explicit: explicit ? fromEnv : undefined,
    configured,
    file: explicit ? undefined : fromEnv,
  });
  if (selected.source === "config") {
    const count = Object.keys(selected.tokens ?? {}).length;
    process.stderr.write(`kxm hub: project tokens from .kxm/config.yaml (${count} projects, key references resolved, values not saved)\n`);
  }
  return selected.tokens;
}

if (!Number.isInteger(port) || port < 0 || port > 65_535) {
  throw new Error("KXM_PORT must be an integer between 0 and 65535");
}
if (![messageTtlMs, messageRetentionMs, rateLimitMax, rateLimitWindowMs].every(Number.isInteger)) {
  throw new Error("message TTL and rate limit settings must be integers");
}

const configuredProjectTokens = projectTokens();
const inlineWorkflows = process.env.KXM_WEBHOOK_WORKFLOWS?.trim();
const workflowFile = process.env.KXM_WEBHOOK_WORKFLOWS_FILE?.trim();
if (inlineWorkflows && workflowFile) {
  throw new Error("configure only one of KXM_WEBHOOK_WORKFLOWS or KXM_WEBHOOK_WORKFLOWS_FILE");
}
const webhookWorkflows = parseWorkflowDefinitions(
  workflowFile ? readFileSync(resolve(workflowFile), "utf8") : inlineWorkflows,
);

const hub = createMeshHub({
  host,
  port,
  dataPath,
  assetsDir,
  messageTtlMs,
  messageRetentionMs,
  rateLimit: { maxRequests: rateLimitMax, windowMs: rateLimitWindowMs },
  ...(authToken ? { authToken } : {}),
  ...(configuredProjectTokens ? { projectTokens: configuredProjectTokens } : {}),
  ...(webhookWorkflows.length ? { webhookWorkflows } : {}),
  logger: structuredLog,
});

const address = await hub.start();
const auth = authToken ? "token" : "none";
if (auth === "none") {
  process.stderr.write("kxm hub: auth=none; every loopback caller is trusted. Set KXM_AUTH_TOKEN before relying on this hub.\n");
}
structuredLog({ event: "hub_started", url: address.url, workspaceDir, configDir, logsDir, assetsDir, stateDir, dataPath, logPath, auth });
process.stdout.write(`kxm hub listening at ${address.url}; storage=${dataPath}; auth=${auth}\n`);

let shutdownPromise: Promise<void> | undefined;
function shutdown(signal: string): Promise<void> {
  if (shutdownPromise) return shutdownPromise;
  shutdownPromise = (async () => {
    structuredLog({ event: "hub_stopping", signal });
    await hub.close();
    structuredLog.close();
    process.exit(0);
  })();
  return shutdownPromise;
}

process.once("SIGINT", () => void shutdown("SIGINT"));
process.once("SIGTERM", () => void shutdown("SIGTERM"));
process.once("message", (message: unknown) => {
  if (message && typeof message === "object" && (message as { type?: string }).type === "shutdown") {
    const signal = (message as { signal?: unknown }).signal;
    void shutdown(typeof signal === "string" ? signal : "parent");
  }
});
