import { Router } from "express";
import { z } from "zod";
import {
  isSystemAdmin,
  requireOrganizationAdmin,
  requireOrganizationMembership,
  requireOrganizationOwner,
} from "../lib/authz";
import { inviteByEmail } from "../lib/invitations";
import { cleanupAfterOrganizationRemoval } from "../lib/membershipCleanup";
import { prisma } from "../lib/prisma";
import { uploadOrganizationLogo } from "../lib/upload";
import { uniqueOrganizationSlug } from "../lib/slugs";
import { HttpError } from "../middleware/errorHandler";
import { requireAuth } from "../middleware/requireAuth";

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

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

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

organizationsRouter.post("/", async (req, res, next) => {
  try {
    const data = createOrganizationSchema.parse(req.body);
    const slug = await uniqueOrganizationSlug(data.name);

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

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

organizationsRouter.get("/:id", async (req, res, next) => {
  try {
    const membership = await requireOrganizationMembership(req.userId!, req.params.id);
    const admin = await isSystemAdmin(req.userId!);
    // Org admins/owners (and system admins) see every workspace, matching the
    // access cascade they actually have. A plain member only sees the specific
    // workspaces they were individually added to — otherwise anyone invited to
    // a single board would see every other department's name/existence too.
    const canSeeAllWorkspaces = admin || membership.role === "ADMIN" || membership.role === "OWNER";

    const organization = await prisma.organization.findUnique({
      where: { id: req.params.id },
      include: {
        members: { include: { user: true }, orderBy: { joinedAt: "asc" } },
        workspaces: {
          orderBy: { createdAt: "asc" },
          include: {
            _count: { select: { boards: true } },
            members: { include: { user: true }, take: 5 },
          },
        },
      },
    });
    if (!organization) throw new HttpError(404, "Organization not found");

    let workspaces = organization.workspaces;
    if (!canSeeAllWorkspaces) {
      const myMemberships = await prisma.workspaceMember.findMany({
        where: { userId: req.userId!, workspaceId: { in: organization.workspaces.map((w) => w.id) } },
        select: { workspaceId: true },
      });
      const visibleIds = new Set(myMemberships.map((m) => m.workspaceId));
      workspaces = organization.workspaces.filter((w) => visibleIds.has(w.id));
    }

    res.json({ organization: { ...organization, workspaces } });
  } catch (err) {
    next(err);
  }
});

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

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

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

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

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

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

// Granting Owner specifically is restricted to existing owners — an admin can't
// hand out the top role themselves, even via an email invite.
organizationsRouter.post("/:id/members", async (req, res, next) => {
  try {
    await requireOrganizationAdmin(req.userId!, req.params.id);
    const data = addMemberSchema.parse(req.body);

    if (data.role === "OWNER") {
      await requireOrganizationOwner(req.userId!, req.params.id);
    }

    const organization = await prisma.organization.findUnique({ where: { id: req.params.id } });
    if (!organization) throw new HttpError(404, "Organization 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,
        role: data.role,
        invitedById: req.userId!,
        inviterName: actor?.name ?? "Someone",
        organizationId: req.params.id,
        organizationName: organization.name,
      });
      res.status(202).json({
        pending: true,
        message: "This person doesn't have an account yet — we've emailed them an invite to join.",
      });
      return;
    }

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

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

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

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

    if (target.role === "OWNER") {
      // Only another owner can remove an owner, and there must always be at least
      // one left standing (otherwise the organization could end up unowned).
      await requireOrganizationOwner(req.userId!, req.params.id);
      const ownerCount = await prisma.organizationMember.count({
        where: { organizationId: req.params.id, role: "OWNER" },
      });
      if (ownerCount <= 1) {
        throw new HttpError(400, "An organization must have at least one owner");
      }
    }

    await prisma.organizationMember.delete({
      where: { organizationId_userId: { organizationId: req.params.id, userId: req.params.userId } },
    });
    await cleanupAfterOrganizationRemoval(req.params.userId, req.params.id);

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