import { Router } from "express";
import { z } from "zod";
import {
  ensureOrganizationMembership,
  isSystemAdmin,
  requireOrganizationMembership,
  requireWorkspaceAdmin,
  requireWorkspaceMembership,
} from "../lib/authz";
import { inviteByEmail } from "../lib/invitations";
import { cleanupAfterWorkspaceRemoval } from "../lib/membershipCleanup";
import { notifyUser } from "../lib/notifications";
import { prisma } from "../lib/prisma";
import { uploadWorkspaceLogo } from "../lib/upload";
import { uniqueWorkspaceSlug } from "../lib/slugs";
import { HttpError } from "../middleware/errorHandler";
import { requireAuth } from "../middleware/requireAuth";

export const workspacesRouter = Router();
workspacesRouter.use(requireAuth);

workspacesRouter.get("/", async (req, res, next) => {
  try {
    const organizationId = typeof req.query.organizationId === "string" ? req.query.organizationId : undefined;
    if (organizationId) {
      await requireOrganizationMembership(req.userId!, organizationId);
    }

    // System admins see every workspace on the platform (or in the given
    // organization), not just ones they've joined.
    const admin = await isSystemAdmin(req.userId!);
    const workspaces = await prisma.workspace.findMany({
      where: {
        ...(organizationId ? { organizationId } : {}),
        ...(admin ? {} : { members: { some: { userId: req.userId } } }),
      },
      include: {
        _count: { select: { boards: true } },
        members: { include: { user: true } },
      },
      orderBy: { createdAt: "asc" },
    });
    res.json({ workspaces });
  } catch (err) {
    next(err);
  }
});

const createWorkspaceSchema = z.object({
  organizationId: z.string().min(1),
  name: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
});

workspacesRouter.post("/", async (req, res, next) => {
  try {
    const data = createWorkspaceSchema.parse(req.body);
    // Any organization member can spin up a new department/workspace within it.
    await requireOrganizationMembership(req.userId!, data.organizationId);
    const slug = await uniqueWorkspaceSlug(data.name);

    const workspace = await prisma.workspace.create({
      data: {
        organizationId: data.organizationId,
        name: data.name,
        description: data.description,
        slug,
        members: { create: { userId: req.userId!, role: "ADMIN" } },
      },
    });

    res.status(201).json({ workspace });
  } catch (err) {
    next(err);
  }
});

workspacesRouter.get("/:id", async (req, res, next) => {
  try {
    await requireWorkspaceMembership(req.userId!, req.params.id);
    const workspace = await prisma.workspace.findUnique({
      where: { id: req.params.id },
      include: {
        members: { include: { user: true } },
        boards: {
          where: { isArchived: false },
          orderBy: { createdAt: "asc" },
          include: {
            members: { include: { user: true }, take: 5 },
            _count: { select: { lists: true } },
          },
        },
      },
    });
    if (!workspace) throw new HttpError(404, "Workspace not found");
    res.json({ workspace });
  } catch (err) {
    next(err);
  }
});

const updateWorkspaceSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  description: z.string().max(500).nullable().optional(),
});

workspacesRouter.patch("/:id", async (req, res, next) => {
  try {
    await requireWorkspaceAdmin(req.userId!, req.params.id);
    const data = updateWorkspaceSchema.parse(req.body);
    const workspace = await prisma.workspace.update({
      where: { id: req.params.id },
      data,
    });
    res.json({ workspace });
  } catch (err) {
    next(err);
  }
});

workspacesRouter.delete("/:id", async (req, res, next) => {
  try {
    await requireWorkspaceAdmin(req.userId!, req.params.id);
    await prisma.workspace.delete({ where: { id: req.params.id } });
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

workspacesRouter.post(
  "/:id/logo",
  uploadWorkspaceLogo.single("logo"),
  async (req, res, next) => {
    try {
      await requireWorkspaceAdmin(req.userId!, req.params.id);
      if (!req.file) throw new HttpError(400, "No file uploaded");

      const logoUrl = `/uploads/workspace-logos/${req.file.filename}`;
      const workspace = await prisma.workspace.update({
        where: { id: req.params.id },
        data: { logoUrl },
      });
      res.json({ workspace });
    } catch (err) {
      next(err);
    }
  }
);

const inviteMemberSchema = z.object({
  email: z.string().email(),
  role: z.enum(["ADMIN", "MEMBER"]).default("MEMBER"),
});

workspacesRouter.post("/:id/members", async (req, res, next) => {
  try {
    await requireWorkspaceAdmin(req.userId!, req.params.id);
    const data = inviteMemberSchema.parse(req.body);

    const workspace = await prisma.workspace.findUnique({ where: { id: req.params.id } });
    if (!workspace) throw new HttpError(404, "Workspace not found");
    const actor = await prisma.user.findUnique({ where: { id: req.userId! } });

    const user = await prisma.user.findUnique({ where: { email: data.email } });
    if (!user) {
      await inviteByEmail({
        email: data.email,
        workspaceId: req.params.id,
        role: data.role,
        invitedById: req.userId!,
        workspaceName: workspace.name,
        inviterName: actor?.name ?? "Someone",
      });
      res.status(202).json({
        pending: true,
        message: "This person doesn't have an account yet — we've emailed them an invite to join.",
      });
      return;
    }

    await ensureOrganizationMembership(user.id, workspace.organizationId);

    const membership = await prisma.workspaceMember.upsert({
      where: { workspaceId_userId: { workspaceId: req.params.id, userId: user.id } },
      update: { role: data.role },
      create: { workspaceId: req.params.id, userId: user.id, role: data.role },
      include: { user: true },
    });

    await notifyUser({
      recipientId: user.id,
      actorId: req.userId!,
      type: "BOARD_INVITE",
      message: `${actor?.name ?? "Someone"} added you to the workspace "${workspace.name}"`,
      workspaceId: req.params.id,
    });

    res.status(201).json({ member: membership });
  } catch (err) {
    next(err);
  }
});

workspacesRouter.delete("/:id/members/:userId", async (req, res, next) => {
  try {
    await requireWorkspaceAdmin(req.userId!, req.params.id);

    const target = await prisma.workspaceMember.findUnique({
      where: { workspaceId_userId: { workspaceId: req.params.id, userId: req.params.userId } },
    });
    if (!target) throw new HttpError(404, "This user is not a member of the workspace");

    if (target.role === "ADMIN") {
      const adminCount = await prisma.workspaceMember.count({
        where: { workspaceId: req.params.id, role: "ADMIN" },
      });
      if (adminCount <= 1) {
        throw new HttpError(400, "A workspace must have at least one admin");
      }
    }

    const boardIds = (
      await prisma.board.findMany({ where: { workspaceId: req.params.id }, select: { id: true } })
    ).map((b) => b.id);

    await prisma.$transaction([
      prisma.boardMember.deleteMany({
        where: { userId: req.params.userId, boardId: { in: boardIds } },
      }),
      prisma.workspaceMember.delete({
        where: { workspaceId_userId: { workspaceId: req.params.id, userId: req.params.userId } },
      }),
    ]);
    await cleanupAfterWorkspaceRemoval(req.params.userId, req.params.id);

    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

workspacesRouter.get("/:id/access-matrix", async (req, res, next) => {
  try {
    await requireWorkspaceAdmin(req.userId!, req.params.id);

    const [members, boards] = await Promise.all([
      prisma.workspaceMember.findMany({
        where: { workspaceId: req.params.id },
        include: { user: true },
        orderBy: { joinedAt: "asc" },
      }),
      prisma.board.findMany({
        where: { workspaceId: req.params.id, isArchived: false },
        include: { members: true },
        orderBy: { createdAt: "asc" },
      }),
    ]);

    res.json({
      users: members.map((m) => ({ ...m.user, workspaceRole: m.role })),
      boards: boards.map((b) => ({
        id: b.id,
        name: b.name,
        visibility: b.visibility,
        access: Object.fromEntries(b.members.map((m) => [m.userId, m.role])),
      })),
    });
  } catch (err) {
    next(err);
  }
});
