/**
 * Org explorer HTTP routes under `/api/org/:orgSlug`.
 */
import type { PrismaClient } from "@prisma/client";
import type { FastifyInstance } from "fastify";
import {
  archiveFolder,
  archiveOrgProjectChatBinding,
  archiveProject,
  createOrgFolder,
  createOrgProject,
  getOrgProjectDetail,
  listMyProjects,
  listOrgExplorer,
  moveOrgProjectToFolder,
  renameFolder,
  renameProject,
} from "../../org/explorer.js";
import { requireOrgRole, requireProjectPermission, ORG_ADMIN_ROLES, type GuardDeps } from "../auth/guards.js";
import { handleRouteError } from "../errors.js";
import { createPermissionAuthorizer } from "../../permissions/authorizer.js";

export interface ExplorerRouteConfig {
  readonly prisma: PrismaClient;
  readonly sessionSecret: string;
  readonly projectWorkspaceRoot: string;
}

export async function registerExplorerRoutes(
  app: FastifyInstance,
  config: ExplorerRouteConfig,
): Promise<void> {
  const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret };

  app.get("/api/org/:orgSlug/explorer", async (request, reply) => {
    try {
      const { orgSlug } = request.params as { orgSlug: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      return await listOrgExplorer(config.prisma, auth.organization.id);
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.get("/api/org/:orgSlug/my-projects", async (request, reply) => {
    try {
      const { orgSlug } = request.params as { orgSlug: string };
      const auth = await requireOrgRole(request, reply, guardDeps, {
        orgSlug,
        roles: ["OWNER", "ADMIN", "MEMBER"],
      });
      if (auth === null) return;
      const projects = await listMyProjects(config.prisma, {
        organizationId: auth.organization.id,
        actorFeishuOpenId: auth.feishuOpenId,
      });
      return { projects };
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.post("/api/org/:orgSlug/folders", async (request, reply) => {
    try {
      const { orgSlug } = request.params as { orgSlug: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = request.body as {
        name?: unknown;
        parentId?: unknown;
        sortKey?: unknown;
      };
      if (typeof body.name !== "string") {
        return reply.status(400).send({
          error: { code: "bad_request", message: "name is required" },
        });
      }
      const folder = await createOrgFolder(config.prisma, {
        organizationId: auth.organization.id,
        name: body.name,
        ...(typeof body.parentId === "string" ? { parentId: body.parentId } : {}),
        ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}),
      });
      return reply.status(201).send({
        id: folder.id,
        name: folder.name,
        parentId: folder.parentId,
        sortKey: folder.sortKey,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.patch("/api/org/:orgSlug/folders/:folderId", async (request, reply) => {
    try {
      const { orgSlug, folderId } = request.params as { orgSlug: string; folderId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = request.body as {
        name?: unknown;
        sortKey?: unknown;
        parentId?: unknown;
      };
      const folder = await renameFolder(config.prisma, {
        organizationId: auth.organization.id,
        folderId,
        ...(typeof body.name === "string" ? { name: body.name } : {}),
        ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}),
        ...(body.parentId === null || typeof body.parentId === "string"
          ? { parentId: body.parentId as string | null }
          : {}),
      });
      return {
        id: folder.id,
        name: folder.name,
        parentId: folder.parentId,
        sortKey: folder.sortKey,
      };
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.post("/api/org/:orgSlug/folders/:folderId/archive", async (request, reply) => {
    try {
      const { orgSlug, folderId } = request.params as { orgSlug: string; folderId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      return await archiveFolder(config.prisma, {
        organizationId: auth.organization.id,
        folderId,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.post("/api/org/:orgSlug/projects", async (request, reply) => {
    try {
      const { orgSlug } = request.params as { orgSlug: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = request.body as { name?: unknown; folderId?: unknown };
      if (typeof body.name !== "string") {
        return reply.status(400).send({
          error: { code: "bad_request", message: "name is required" },
        });
      }
      const result = await createOrgProject(config.prisma, {
        organizationId: auth.organization.id,
        actorFeishuOpenId: auth.feishuOpenId,
        name: body.name,
        workspaceRoot: config.projectWorkspaceRoot,
        ...(typeof body.folderId === "string" ? { folderId: body.folderId } : {}),
      });
      return reply.status(201).send({ id: result.projectId, name: body.name });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.get("/api/org/:orgSlug/projects/:projectId", async (request, reply) => {
    try {
      const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string };
      const auth = await requireProjectPermission(request, reply, guardDeps, {
        orgSlug,
        projectId,
        action: "project.read",
        allowOrgAdminOversight: true,
      });
      if (auth === null) return;
      const detail = await getOrgProjectDetail(config.prisma, {
        organizationId: auth.organization.id,
        projectId,
      });
      const manageDecision = await createPermissionAuthorizer(config.prisma).can({
        actor: { feishuOpenId: auth.feishuOpenId },
        action: "collaborator.manage",
        resource: { type: "PROJECT", id: projectId },
      });
      return {
        ...detail,
        actorIsOrgAdmin: ORG_ADMIN_ROLES.includes(auth.membershipRole),
        actorCanManageProject: manageDecision.allowed,
      };
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.patch("/api/org/:orgSlug/projects/:projectId", async (request, reply) => {
    try {
      const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = request.body as { name?: unknown };
      if (typeof body.name !== "string") {
        return reply.status(400).send({
          error: { code: "bad_request", message: "name is required" },
        });
      }
      return await renameProject(config.prisma, {
        organizationId: auth.organization.id,
        projectId,
        name: body.name,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.patch("/api/org/:orgSlug/projects/:projectId/folder", async (request, reply) => {
    try {
      const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = request.body as { folderId?: unknown };
      if (body.folderId !== null && typeof body.folderId !== "string") {
        return reply.status(400).send({
          error: { code: "bad_request", message: "folderId must be string or null" },
        });
      }
      return await moveOrgProjectToFolder(config.prisma, {
        organizationId: auth.organization.id,
        projectId,
        folderId: body.folderId as string | null,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.post("/api/org/:orgSlug/projects/:projectId/archive", async (request, reply) => {
    try {
      const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      return await archiveProject(config.prisma, {
        organizationId: auth.organization.id,
        projectId,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });

  app.post("/api/org/:orgSlug/projects/:projectId/binding/archive", async (request, reply) => {
    try {
      const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      return await archiveOrgProjectChatBinding(config.prisma, {
        organizationId: auth.organization.id,
        projectId,
        actorFeishuOpenId: auth.feishuOpenId,
      });
    } catch (err) {
      return handleRouteError(reply, err);
    }
  });
}
