import Fastify from "fastify";
import { registerAdminPlugin } from "./admin/plugin.js";
import { registerDatabasePlugin } from "./database/plugin.js";
import { prisma } from "./db.js";
import { createLarkClient, startFeishuListenerWithClient } from "./feishu/client.js";
import { archiveFeishuBindingForLifecycleEvent } from "./feishu/bindingLifecycle.js";
import { makeTriggerHandler } from "./feishu/trigger.js";
import { removeAbandonedMessageResourceStages } from "./feishu/resourceStaging.js";
import { triggerQueue } from "./feishu/triggerQueue.js";
import { LocalSecretEnvelope, loadLocalSecretKeyring } from "./security/secretEnvelope.js";
import { createDatabaseRuntimeSettings } from "./settings/runtime.js";
import { verifyStoredProviderEnvelopes } from "./connections/providerConnections.js";
import { openProviderProxyLease } from "./connections/providerProxy.js";
import { verifyStoredFeishuApplicationEnvelopes } from "./connections/feishuApplicationConnections.js";
import { resolveActiveFeishuApplication } from "./connections/feishuApplicationConnections.js";
import { readServerBinding } from "./settings/server.js";
import { readSiloOrganizationId, requireSiloOrganization } from "./deployment/silo.js";
import { isSiloHttpRateLimitExempt, SiloFixedWindowRateLimiter } from "./deployment/siloRateLimit.js";

function requireEnv(name: string): string {
  const value = process.env[name];
  if (value === undefined || value === "") {
    throw new Error(`missing required env: ${name}`);
  }
  return value;
}

function booleanEnv(name: string, fallback: boolean): boolean {
  const raw = process.env[name];
  if (raw === undefined || raw === "") return fallback;
  return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
}

/** Start the Hub after every import and runtime dependency has loaded. */
export async function startHub(): Promise<void> {
  requireEnv("DATABASE_URL");
  const projectWorkspaceRoot = requireEnv("HUB_PROJECT_WORKSPACE_ROOT");
  const httpBodyLimit = positiveIntegerEnv("HUB_HTTP_BODY_LIMIT_BYTES");
  const maxFilesPerMessage = positiveIntegerEnv("HUB_MAX_FILES_PER_MESSAGE");
  const maxBytesPerFile = positiveIntegerEnv("HUB_MAX_FILE_BYTES");
  const httpRequestsPerMinute = positiveIntegerEnv("HUB_HTTP_REQUESTS_PER_MINUTE");
  const feishuEventsPerMinute = positiveIntegerEnv("HUB_FEISHU_EVENTS_PER_MINUTE");
  const app = Fastify({ logger: true, bodyLimit: httpBodyLimit });
  const requestLimiter = new SiloFixedWindowRateLimiter(httpRequestsPerMinute, 60_000);
  app.addHook("onRequest", async (request, reply) => {
    // Static SPA assets / admin HTML shell / healthz do not count toward the
    // silo requestRate budget (a full admin load can pull dozens of chunks).
    if (isSiloHttpRateLimitExempt(request.url)) return;
    const decision = requestLimiter.consume();
    if (!decision.allowed) {
      await reply
        .header("Retry-After", String(decision.retryAfterSeconds))
        .status(429)
        .send({ error: { code: "rate_limited", message: "Silo request rate exceeded" } });
    }
  });

  const abandonedStages = await removeAbandonedMessageResourceStages(projectWorkspaceRoot);
  app.log.info({ removed: abandonedStages }, "startup: removed abandoned Feishu resource stages");

  const secretEnvelope = new LocalSecretEnvelope(await loadLocalSecretKeyring());
  const verifiedProviderEnvelopes = await verifyStoredProviderEnvelopes(prisma, secretEnvelope);
  const verifiedFeishuEnvelopes = await verifyStoredFeishuApplicationEnvelopes(prisma, secretEnvelope);
  app.log.info(
    { provider: verifiedProviderEnvelopes, feishu: verifiedFeishuEnvelopes },
    "startup: authenticated stored connection envelopes",
  );
  const runtimeSettings = createDatabaseRuntimeSettings(
    prisma,
    secretEnvelope,
    process.env,
    (credential, context) => openProviderProxyLease(credential, {
      onDiagnostic: (diagnostic) => {
        app.log.error({
          runId: context.runId,
          projectId: context.projectId,
          providerId: context.providerId,
          errorCode: diagnostic.code,
          failureCategory: diagnostic.category,
        }, "provider proxy diagnostic");
      },
    }),
  );

  const siloOrganizationId = readSiloOrganizationId();
  const siloOrganization = await requireSiloOrganization(prisma, siloOrganizationId);
  const activeProvider = await prisma.organizationProviderConnection.findUnique({
    where: {
      organizationId_providerId: {
        organizationId: siloOrganization.id,
        providerId: "openrouter",
      },
    },
    select: {
      status: true,
      activeSecretVersion: { select: { connectionId: true, retiredAt: true } },
      id: true,
    },
  });
  if (activeProvider?.status !== "ACTIVE" || activeProvider.activeSecretVersion === null ||
      activeProvider.activeSecretVersion.connectionId !== activeProvider.id ||
      activeProvider.activeSecretVersion.retiredAt !== null) {
    throw new Error(`Silo Organization ${siloOrganization.id} has no ACTIVE openrouter Provider Connection`);
  }
  const feishuApplication = await resolveActiveFeishuApplication(
    prisma,
    secretEnvelope,
    { organizationId: siloOrganization.id },
  );
  app.log.info(
    {
      organizationId: siloOrganization.id,
      organizationSlug: siloOrganization.slug,
      feishuConnectionId: feishuApplication.connectionId,
    },
    "startup: proved Silo Organization and resolved Feishu Application",
  );
  const sessionSecret = requireEnv("HUB_SESSION_SECRET");
  const publicBaseUrl = process.env["HUB_PUBLIC_BASE_URL"] ?? "http://127.0.0.1:8788";
  const bind = readServerBinding();

  // Startup reset: clear stale locks + mark dead runs as FAILED.
  await prisma.projectAgentLock.deleteMany({});
  await prisma.agentRun.updateMany({
    where: { status: "ACTIVE" },
    data: { status: "FAILED", error: "process restart", finishedAt: new Date() },
  });
  app.log.info("startup: cleared stale locks + dead runs");

  let feishuRuntime: { readonly isListenerReady?: () => boolean } | undefined;
  app.get("/api/healthz", async (_request, reply) => {
    const feishuReady = feishuRuntime?.isListenerReady?.() ?? !booleanEnv("HUB_FEISHU_LISTENER_ENABLED", true);
    if (!feishuReady) return reply.status(503).send({ ok: false, feishuReady, ts: Date.now() });
    return { ok: true, feishuReady, ts: Date.now() };
  });

  await registerAdminPlugin(app, {
    prisma,
    sessionSecret,
    publicBaseUrl,
    feishuAppId: feishuApplication.appId,
    feishuAppSecret: feishuApplication.appSecret,
    projectWorkspaceRoot,
    secretEnvelope,
  });

  // `/database/*` surface. Registered after the admin plugin so the
  // @fastify/cookie parser and /auth/* login routes are already available.
  await registerDatabasePlugin(app, {
    prisma,
    sessionSecret,
    siloOrganizationSlug: siloOrganization.slug,
  });

  const feishuListenerEnabled = booleanEnv("HUB_FEISHU_LISTENER_ENABLED", true);
  if (feishuListenerEnabled) {
    const feishuConfig = {
      appId: feishuApplication.appId,
      appSecret: feishuApplication.appSecret,
      botOpenId: feishuApplication.botOpenId,
    };
    const larkClient = createLarkClient(feishuConfig);
    const triggerQueuePurgeTimer = setInterval(() => {
      const removed = triggerQueue.purgeExpired();
      if (removed > 0) {
        app.log.info({ removed }, "feishu trigger queue: purged expired triggers");
      }
    }, 60_000);
    triggerQueuePurgeTimer.unref();
    const trigger = makeTriggerHandler({
      prisma,
      settings: runtimeSettings,
      logger: app.log,
      secretEnvelope,
      projectWorkspaceRoot,
      publicBaseUrl,
      siloOrganizationId: siloOrganization.id,
      rejectWhenBusy: true,
      resourceLimits: { maxFilesPerMessage, maxBytesPerFile },
      allowLegacyFeishuIdentity: false,
      maxFeishuEventsPerMinute: feishuEventsPerMinute,
    });
    feishuRuntime = await startFeishuListenerWithClient(
      feishuConfig,
      larkClient,
      app.log,
      trigger,
      trigger.onCardAction,
      () => {
        process.exitCode = 1;
        void app.close().catch((error) => app.log.error({ err: error }, "Hub close after Feishu failure failed"));
      },
      async (event) => {
        const result = await archiveFeishuBindingForLifecycleEvent(prisma, event);
        app.log.info({ ...event, archived: result.archived, projectId: result.projectId }, "feishu binding lifecycle event handled");
      },
    );
  } else {
    app.log.info("feishu listener disabled by HUB_FEISHU_LISTENER_ENABLED");
  }
  const address = await app.listen(bind);
  app.log.info({ address }, "hub listening");
}

function positiveIntegerEnv(name: string): number {
  const raw = requireEnv(name);
  const value = Number(raw);
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer`);
  return value;
}
