import Fastify, { type FastifyInstance } from "fastify";
import { ZodError } from "zod";

import type { AppConfig } from "./config.js";
import { createPool, DatabaseNotConfiguredError } from "./db/pool.js";
import { ToolExecutionInputError } from "./mcp/tool-runtime.js";
import { AgentsRepository } from "./repositories/agents.js";
import { registerAgentRoutes } from "./routes/agents.js";
import { registerEngineGatewayRoutes } from "./routes/engine.js";
import { registerHealthRoutes } from "./routes/health.js";
import { registerInstallerRoutes } from "./routes/install.js";
import { registerMcpRoutes } from "./routes/mcp.js";
import { registerOntologyGatewayRoutes } from "./routes/ontology.js";
import { registerPublicRoutes } from "./routes/public.js";
import { registerToolRoutes } from "./routes/tools.js";
import { ForbiddenError } from "./security/authorization.js";
import { UnauthorizedError } from "./security/bearer.js";
import { InternalAuthNotConfiguredError } from "./security/internal.js";
import { TaskerAdapterError, TaskerAdapterNotConfiguredError, TaskerAdapterUnavailableError, TaskerClient } from "./tasker/client.js";

export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
  const pool = createPool(config);
  const agentsRepository = pool ? new AgentsRepository(pool) : null;
  const taskerClient = new TaskerClient({
    baseUrl: config.NODEDC_TASKER_INTERNAL_URL,
    internalAccessToken: config.NODEDC_INTERNAL_ACCESS_TOKEN,
  });
  const app = Fastify({
    bodyLimit: 10 * 1024 * 1024,
    logger: {
      level: config.LOG_LEVEL,
    },
  });

  app.addHook("onClose", async () => {
    await pool?.end();
  });

  app.setErrorHandler((error, _request, reply) => {
    if (error instanceof ZodError) {
      void reply.status(400).send({
        ok: false,
        error: "validation_error",
        details: error.issues,
      });
      return;
    }

    if (error instanceof DatabaseNotConfiguredError) {
      void reply.status(503).send({
        ok: false,
        error: "database_not_configured",
        message: "DATABASE_URL is required for Agent Gateway persistence endpoints.",
      });
      return;
    }

    if (error instanceof UnauthorizedError) {
      void reply.status(401).send({
        ok: false,
        error: "unauthorized",
        message: error.message,
      });
      return;
    }

    if (error instanceof InternalAuthNotConfiguredError) {
      void reply.status(503).send({
        ok: false,
        error: "internal_auth_not_configured",
        message: error.message,
      });
      return;
    }

    if (error instanceof ForbiddenError) {
      void reply.status(403).send({
        ok: false,
        error: "forbidden",
        message: error.message,
      });
      return;
    }

    if (error instanceof ToolExecutionInputError) {
      void reply.status(error.httpStatus).send({
        ok: false,
        error: error.code,
        message: error.message,
        details: error.details,
      });
      return;
    }

    if (error instanceof TaskerAdapterNotConfiguredError) {
      void reply.status(503).send({
        ok: false,
        error: "tasker_adapter_not_configured",
        message: error.message,
      });
      return;
    }

    if (error instanceof TaskerAdapterError) {
      void reply.status(error.statusCode).send({
        ok: false,
        error: "tasker_adapter_error",
        message: error.message,
        tasker_status: error.statusCode,
        tasker_payload: error.payload,
      });
      return;
    }

    if (error instanceof TaskerAdapterUnavailableError) {
      void reply.status(502).send({
        ok: false,
        error: "tasker_adapter_unavailable",
        message: error.message,
      });
      return;
    }

    app.log.error(error);
    void reply.status(500).send({
      ok: false,
      error: "internal_server_error",
      message: "Agent Gateway request failed.",
    });
  });

  await registerPublicRoutes(app);
  await registerInstallerRoutes(app, { publicUrl: config.NODEDC_AGENT_GATEWAY_PUBLIC_URL });
  await registerHealthRoutes(app, config, pool);
  await registerAgentRoutes(app, {
    agentsRepository,
    codexInstallChannel: config.NODEDC_OPS_CODEX_INSTALL_CHANNEL,
    codexNpmSpec: config.NODEDC_OPS_CODEX_NPM_SPEC,
    publicUrl: config.NODEDC_AGENT_GATEWAY_PUBLIC_URL,
    internalAccessToken: config.NODEDC_AGENT_GATEWAY_INTERNAL_TOKEN,
    aiWorkspaceRunTokenTtlSeconds: config.NODEDC_AI_WORKSPACE_RUN_TOKEN_TTL_SECONDS,
  });
  await registerEngineGatewayRoutes(app, {
    engineInternalUrl: config.NODEDC_ENGINE_INTERNAL_URL,
    publicUrl: config.NODEDC_AGENT_GATEWAY_PUBLIC_URL,
  });
  await registerToolRoutes(app, { agentsRepository, taskerClient });
  await registerMcpRoutes(app, { agentsRepository, taskerClient });
  await registerOntologyGatewayRoutes(app, {
    agentsRepository,
    ontologyCoreUrl: config.NODEDC_ONTOLOGY_CORE_URL,
    ontologyCoreAccessToken: config.NODEDC_ONTOLOGY_CORE_ACCESS_TOKEN ?? config.NODEDC_INTERNAL_ACCESS_TOKEN,
  });

  return app;
}
