import Fastify from "fastify";
import cookie from "@fastify/cookie";
import cors from "@fastify/cors";
import jwt from "@fastify/jwt";
import multipart from "@fastify/multipart";
import sensible from "@fastify/sensible";
import websocket from "@fastify/websocket";
import { env } from "./env.js";
import { prisma } from "./lib/prisma.js";
import { ensureSystemSeed } from "./lib/system-seed.js";
import { registerAuthRoutes } from "./modules/auth/routes.js";
import { registerDashboardRoutes } from "./modules/dashboard/routes.js";
import { registerClientRoutes } from "./modules/clients/routes.js";
import { registerCaseRoutes } from "./modules/cases/routes.js";
import { registerAiRoutes } from "./modules/ai/routes.js";
import { registerUserRoutes } from "./modules/users/routes.js";
import { registerRepositoryRoutes } from "./modules/repository/routes.js";
import { registerFileRoutes } from "./modules/files/routes.js";
import { registerFormRoutes } from "./modules/forms/routes.js";
import { registerWorkflowRoutes } from "./modules/workflows/routes.js";
import { registerIntelligenceRoutes } from "./modules/intelligence/routes.js";
import { registerFormEngineRoutes } from "./modules/form-engine/routes.js";
import { registerPortalRoutes } from "./modules/portal/routes.js";
import { registerDocumentReviewRoutes } from "./modules/document-reviews/routes.js";
import { registerTeamRoutes } from "./modules/teams/routes.js";
import { registerTeamMessageRoutes } from "./modules/team-messages/routes.js";
import {
  registerKommoIntegrationRoutes,
  registerKommoWebhookRoutes,
} from "./modules/integrations/kommo/routes.js";
import { registerNeuraHubIntegrationRoutes } from "./modules/integrations/neurahub/routes.js";

function megabytesToBytes(value: number) {
  return value * 1024 * 1024;
}

function buildAllowedOrigins(frontendUrl: string) {
  const allowedOrigins = new Set<string>();

  try {
    const parsed = new URL(frontendUrl);
    allowedOrigins.add(parsed.origin);

    if (parsed.hostname === "localhost") {
      const localhostAlias = new URL(parsed.toString());
      localhostAlias.hostname = "127.0.0.1";
      allowedOrigins.add(localhostAlias.origin);
    } else if (parsed.hostname === "127.0.0.1") {
      const loopbackAlias = new URL(parsed.toString());
      loopbackAlias.hostname = "localhost";
      allowedOrigins.add(loopbackAlias.origin);
    }
  } catch {
    allowedOrigins.add(frontendUrl.replace(/\/+$/, ""));
  }

  return allowedOrigins;
}

export function buildApp() {
  const bodyLimitBytes = megabytesToBytes(env.API_BODY_LIMIT_MB);
  const multipartFileLimitBytes = megabytesToBytes(env.API_MULTIPART_FILE_LIMIT_MB);

  const app = Fastify({
    logger: true,
    bodyLimit: bodyLimitBytes,
  });

  const allowedOrigins = buildAllowedOrigins(env.FRONTEND_URL);

  app.register(cors, {
    origin(origin, callback) {
      if (!origin || allowedOrigins.has(origin)) {
        callback(null, true);
        return;
      }

      callback(new Error("Origin not allowed by CORS"), false);
    },
    credentials: true,
    methods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
  });
  app.register(cookie);
  app.register(jwt, {
    secret: env.JWT_SECRET,
    cookie: {
      cookieName: env.COOKIE_NAME,
      signed: false,
    },
  });
  app.register(multipart, {
    attachFieldsToBody: false,
    limits: {
      fileSize: multipartFileLimitBytes,
      files: env.API_MULTIPART_FILE_COUNT_LIMIT,
      parts: env.API_MULTIPART_PARTS_LIMIT,
    },
  });
  app.register(sensible);
  app.register(websocket);

  app.setErrorHandler((error, _request, reply) => {
    const statusCode = Number(
      typeof (error as { statusCode?: unknown }).statusCode === "number"
        ? (error as { statusCode: number }).statusCode
        : 500,
    );
    const errorCode = String((error as { code?: unknown }).code ?? "");
    const exceedsUploadLimit =
      statusCode === 413 ||
      errorCode === "FST_REQ_FILE_TOO_LARGE" ||
      errorCode === "FST_PART_LIMIT" ||
      errorCode === "FST_FILES_LIMIT" ||
      errorCode === "FST_REQ_BODY_TOO_LARGE";

    if (exceedsUploadLimit) {
      reply.code(413).send({
        message: `Upload limit exceeded. Current server limits: ${env.API_BODY_LIMIT_MB} MB per request, ${env.API_MULTIPART_FILE_LIMIT_MB} MB per file, up to ${env.API_MULTIPART_FILE_COUNT_LIMIT} files per batch.`,
      });
      return;
    }

    reply.send(error);
  });

  app.get("/health", async () => {
    await prisma.$queryRaw`SELECT 1`;

    return {
      service: "neurav2-api",
      status: "ok",
      timestamp: new Date().toISOString(),
    };
  });

  app.register(registerAuthRoutes, { prefix: "/auth" });
  app.register(registerDashboardRoutes, { prefix: "/dashboard" });
  app.register(registerUserRoutes, { prefix: "/users" });
  app.register(registerTeamRoutes, { prefix: "/team" });
  app.register(registerTeamMessageRoutes, { prefix: "/team/messages" });
  app.register(registerClientRoutes, { prefix: "/clients" });
  app.register(registerCaseRoutes, { prefix: "/cases" });
  app.register(registerAiRoutes, { prefix: "/ai" });
  app.register(registerRepositoryRoutes, { prefix: "/repository" });
  app.register(registerFileRoutes, { prefix: "/files" });
  app.register(registerFormRoutes, { prefix: "/forms" });
  app.register(registerWorkflowRoutes, { prefix: "/workflows" });
  app.register(registerIntelligenceRoutes, { prefix: "/intelligence" });
  app.register(registerFormEngineRoutes, { prefix: "/form-engine" });
  app.register(registerPortalRoutes, { prefix: "/portal" });
  app.register(registerDocumentReviewRoutes, { prefix: "/document-reviews" });
  app.register(registerKommoIntegrationRoutes, { prefix: "/integrations/kommo" });
  app.register(registerNeuraHubIntegrationRoutes, { prefix: "/integrations/neurahub" });
  app.register(registerKommoWebhookRoutes, { prefix: "/webhook/kommo" });

  app.addHook("onReady", async () => {
    await ensureSystemSeed();
  });

  app.addHook("onClose", async () => {
    await prisma.$disconnect();
  });

  return app;
}
