import type { Server } from "bun";
import postgres from "postgres";
import { readConfig } from "./config";
import { ctx, sql, config, logger } from "./context";
import { createLogger } from "./logger";
import { initDatabase } from "./db";
import { getNodes, getNodeDockerStatuses, getNodeSystemStatuses, getEvents, getTasks, getTask } from "./db";
import { recordRequestPerformance, withPerformanceOperation, getPerformance } from "./performance";
import { handleProviderMessage, markProviderOffline, markStaleProvidersOffline } from "./provider-registry";
import { markStaleTasksFailed, dispatchTask } from "./task-dispatcher";
import { handleSshClientMessage, sshRoute } from "./ssh-bridge";
import { closeEgressTcpConnectionsForProvider, closeEgressTcpConnectionsForSocket } from "./egress-tcp";
import { scheduledTaskRoute, runDueScheduledTasks, recoverScheduledRuns } from "./scheduler";
import { microserviceRoute, getMicroservices } from "./microservice-proxy";
import { getOverview, codexQueueLoadTest } from "./overview";
import { jsonResponse, textResponse, readLimit, errorToJson } from "./http";
import type { WsData, ProviderSocket } from "./types";

const runtimeConfig = readConfig();
const { logger: loggerFn, recentLogs } = createLogger("backend-core", runtimeConfig.logFile);
const serverIdleTimeoutSeconds = 120;

ctx.config = runtimeConfig;
ctx.logger = loggerFn;
ctx.recentLogs = recentLogs;
ctx.sql = postgres(runtimeConfig.databaseUrl, {
  max: runtimeConfig.databasePoolMax,
  idle_timeout: 20,
  connect_timeout: 10,
  connection: { application_name: "unidesk-backend-core" },
});

(async () => {
  try {
    await initDatabase(ctx.sql!);
    ctx.dbReady = true;
    logger("info", "database_ready", undefined);
    await recoverScheduledRuns();
  } catch (error) {
    logger("error", "database_init_failed", errorToJson(error));
    setTimeout(() => process.exit(1), 1000);
  }
})();

function isDevIdentity(): boolean {
  const identity = config().identity;
  return identity.environment === "dev" || identity.namespace === "unidesk-dev";
}

function healthPayload(): Record<string, unknown> {
  const base = { ok: true, service: "unidesk-core", dbReady: ctx.dbReady, startedAt: ctx.serviceStartedAt.toISOString() };
  if (!isDevIdentity()) return base;
  const identity = config().identity;
  return {
    ...base,
    environment: identity.environment,
    namespace: identity.namespace,
    databaseName: identity.databaseName,
    serviceId: identity.serviceId,
    deployRef: identity.deployRef,
    deploy: {
      repo: identity.repo,
      commit: identity.commit,
      requestedCommit: identity.requestedCommit,
    },
  };
}

async function routeInner(req: Request, server: Server<WsData>): Promise<Response | undefined> {
  const url = new URL(req.url);
  if (req.method === "OPTIONS") return jsonResponse({ ok: true });

  try {
    if (url.pathname === "/ws/ssh") return sshRoute(req, server);
    if (url.pathname === "/" || url.pathname === "/health") {
      return jsonResponse(healthPayload());
    }
    if (!ctx.dbReady && url.pathname.startsWith("/api/")) {
      return jsonResponse({ ok: false, error: "database not ready" }, 503);
    }
    if (url.pathname === "/api/overview") return jsonResponse(await withPerformanceOperation("core", "overview", url.pathname, () => getOverview()));
    if (url.pathname === "/api/nodes") return jsonResponse({ ok: true, nodes: await withPerformanceOperation("core", "nodes", url.pathname, () => getNodes()) });
    if (url.pathname === "/api/nodes/system-status") return jsonResponse({ ok: true, systemStatuses: await withPerformanceOperation("core", "node_system_status", url.search, () => getNodeSystemStatuses(readLimit(url, 60))) });
    if (url.pathname === "/api/nodes/docker-status") return jsonResponse({ ok: true, dockerStatuses: await withPerformanceOperation("core", "node_docker_status", url.pathname, () => getNodeDockerStatuses()) });
    if (url.pathname === "/api/events") return jsonResponse({ ok: true, events: await withPerformanceOperation("core", "events", url.search, () => getEvents(readLimit(url, 100))) });
    if (url.pathname.startsWith("/api/tasks/") && req.method === "GET") {
      const taskId = decodeURIComponent(url.pathname.slice("/api/tasks/".length));
      if (taskId.length === 0 || taskId.includes("/")) return jsonResponse({ ok: false, error: "invalid task id" }, 400);
      const task = await withPerformanceOperation("core", "task_detail", taskId, () => getTask(taskId));
      if (task === null) return jsonResponse({ ok: false, error: `task not found: ${taskId}` }, 404);
      return jsonResponse({ ok: true, task });
    }
    if (url.pathname === "/api/tasks") {
      const lite = ["1", "true", "yes"].includes((url.searchParams.get("lite") ?? "").toLowerCase());
      const summary = ["1", "true", "yes"].includes((url.searchParams.get("summary") ?? "").toLowerCase());
      return jsonResponse({ ok: true, tasks: await withPerformanceOperation("core", "tasks", url.search, () => getTasks(readLimit(url, 100), url.searchParams.get("status") ?? "all", lite, summary)) });
    }
    if (url.pathname === "/api/schedules" || url.pathname.startsWith("/api/schedules/")) {
      return withPerformanceOperation("scheduler", "schedules", url.pathname, () => scheduledTaskRoute(req, url));
    }
    if (url.pathname === "/api/microservices") return jsonResponse({ ok: true, microservices: await withPerformanceOperation("core", "microservices", url.pathname, () => getMicroservices()) });
    if (url.pathname === "/api/performance") return jsonResponse(await getPerformance());
    if (url.pathname === "/api/code-queue-load-test" && (req.method === "GET" || req.method === "POST")) return withPerformanceOperation("performance", "code_queue_load_test", url.pathname, () => codexQueueLoadTest(req));
    if (url.pathname.startsWith("/api/microservices/")) return withPerformanceOperation("microservices", "route", url.pathname, () => microserviceRoute(req, url));
    if (url.pathname === "/api/dispatch" && req.method === "POST") return withPerformanceOperation("scheduler", "dispatch", url.pathname, () => dispatchTask(req));
    if (url.pathname === "/logs") return jsonResponse({ ok: true, logs: ctx.recentLogs.slice(-readLimit(url, 100)) });
    if (url.pathname === "/favicon.ico") return textResponse("", 204);
    return jsonResponse({ ok: false, error: "not found", path: url.pathname }, 404);
  } catch (error) {
    logger("error", "request_failed", { path: url.pathname, error: errorToJson(error) });
    return jsonResponse({ ok: false, error: errorToJson(error) }, 500);
  }
}

async function route(req: Request, server: Server<WsData>): Promise<Response | undefined> {
  const started = performance.now();
  const url = new URL(req.url);
  let response: Response | undefined;
  try {
    response = await routeInner(req, server);
    return response;
  } finally {
    recordRequestPerformance(req, url.pathname, response, performance.now() - started);
  }
}

async function providerRoute(req: Request, server: Server<WsData>): Promise<Response | undefined> {
  const url = new URL(req.url);
  if (url.pathname === "/" || url.pathname === "/health") {
    return jsonResponse({
      ok: true,
      service: "unidesk-provider-ingress",
      activeSocketCount: ctx.activeProviders.size,
    });
  }
  if (url.pathname === "/ws/provider") {
    const token = url.searchParams.get("token") ?? req.headers.get("x-provider-token");
    if (token !== config().providerToken) return jsonResponse({ ok: false, error: "invalid provider token" }, 401);
    const upgraded = server.upgrade(req, { data: { channel: "provider" } satisfies WsData });
    return upgraded ? undefined : jsonResponse({ ok: false, error: "websocket upgrade failed" }, 400);
  }
  return jsonResponse({ ok: false, error: "not found" }, 404);
}

const apiServer = Bun.serve<WsData>({
  port: config().port,
  idleTimeout: serverIdleTimeoutSeconds,
  fetch(req, server) {
    return route(req, server);
  },
  websocket: {
    open(ws) {
      if (ws.data.channel === "ssh") return;
      logger("info", "api_socket_open", { remoteAddress: ws.remoteAddress, channel: ws.data.channel ?? null });
    },
    message(ws, raw) {
      if (ws.data.channel === "ssh") {
        handleSshClientMessage(ws, raw).catch((error) => {
          logger("error", "ssh_client_message_failed", { sessionId: ws.data.sessionId ?? null, error: errorToJson(error) });
        });
        return;
      }
    },
    close(ws) {
      if (ws.data.channel === "ssh") return;
    },
  },
});

const providerServer = Bun.serve<WsData>({
  port: config().providerPort,
  idleTimeout: serverIdleTimeoutSeconds,
  async fetch(req, server) {
    const started = performance.now();
    const url = new URL(req.url);
    let response: Response | undefined;
    try {
      response = await providerRoute(req, server) as Response | undefined;
      return response;
    } finally {
      recordRequestPerformance(req, url.pathname, response, performance.now() - started);
    }
  },
  websocket: {
    open(ws) {
      logger("info", "provider_socket_open", { remoteAddress: ws.remoteAddress });
    },
    message(ws, raw) {
      handleProviderMessage(ws, raw).catch((error) => {
        logger("error", "provider_message_failed", { providerId: ws.data.providerId ?? null, error: errorToJson(error) });
        ws.send(JSON.stringify({ type: "ack", requestId: "message", ok: false, message: String(error) }));
      });
    },
    close(ws) {
      const providerId = ws.data.providerId;
      logger("warn", "provider_socket_close", { providerId: providerId ?? null });
      if (providerId !== undefined) {
        if (ctx.activeProviders.get(providerId) !== ws) {
          closeEgressTcpConnectionsForSocket(ws);
          logger("info", "provider_socket_close_ignored_replaced", { providerId });
          return;
        }
        closeEgressTcpConnectionsForProvider(providerId);
        for (const [requestId, waiter] of ctx.httpTunnelWaiters) {
          if (requestId.startsWith(`${providerId}:`)) {
            ctx.httpTunnelWaiters.delete(requestId);
            waiter(null, "provider-disconnected");
          }
        }
        markProviderOffline(providerId).catch((error) => logger("error", "provider_offline_mark_failed", { providerId, error: errorToJson(error) }));
      }
    },
  },
});

setInterval(() => {
  markStaleProvidersOffline().catch((error) => logger("error", "heartbeat_sweep_failed", { error: errorToJson(error) }));
}, 10_000);

setInterval(() => {
  markStaleTasksFailed().catch((error) => logger("error", "task_timeout_sweep_failed", { error: errorToJson(error) }));
}, Math.min(config().taskPendingTimeoutMs, 60_000));

setInterval(() => {
  runDueScheduledTasks().catch((error) => logger("error", "scheduled_task_sweep_failed", { error: errorToJson(error) }));
}, 30_000);

logger("info", "server_listening", {
  apiUrl: `http://0.0.0.0:${apiServer.port}`,
  providerIngressUrl: `ws://0.0.0.0:${providerServer.port}/ws/provider`,
  logFile: config().logFile,
});
