import fs from "fs/promises";
import path from "path";
import { Router } from "express";
import { z } from "zod";
import { isSystemAdmin, requireWorkspaceMembership } from "../lib/authz";
import { notifyUser } from "../lib/notifications";
import { prisma } from "../lib/prisma";
import { uploadDriveFile, uploadRoot } from "../lib/upload";
import { HttpError } from "../middleware/errorHandler";
import { requireAuth } from "../middleware/requireAuth";

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

const fileInclude = {
  owner: true,
  shares: { include: { user: true } },
};

const TEXT_FILE_MIME_TYPES = new Set(["text/plain", "text/markdown"]);

async function isWorkspaceAdmin(userId: string, workspaceId: string): Promise<boolean> {
  if (await isSystemAdmin(userId)) return true;
  const membership = await prisma.workspaceMember.findUnique({
    where: { workspaceId_userId: { workspaceId, userId } },
  });
  return membership?.role === "ADMIN";
}

async function requireFolderAccess(userId: string, folderId: string, needManage = false) {
  const folder = await prisma.driveFolder.findUnique({ where: { id: folderId } });
  if (!folder) throw new HttpError(404, "Folder not found");
  await requireWorkspaceMembership(userId, folder.workspaceId);

  if (needManage) {
    const admin = await isWorkspaceAdmin(userId, folder.workspaceId);
    if (!admin && folder.ownerId !== userId) {
      throw new HttpError(403, "You don't have permission to manage this folder");
    }
  }

  return folder;
}

// Walks up the parent chain so the frontend can render a clickable breadcrumb trail.
async function buildBreadcrumb(folderId: string): Promise<{ id: string; name: string }[]> {
  const trail: { id: string; name: string }[] = [];
  let current = await prisma.driveFolder.findUnique({ where: { id: folderId } });
  while (current) {
    trail.unshift({ id: current.id, name: current.name });
    current = current.parentId
      ? await prisma.driveFolder.findUnique({ where: { id: current.parentId } })
      : null;
  }
  return trail;
}

async function requireFileAccess(userId: string, fileId: string, needEdit = false) {
  const file = await prisma.driveFile.findUnique({ where: { id: fileId }, include: fileInclude });
  if (!file) throw new HttpError(404, "File not found");

  const admin = await isWorkspaceAdmin(userId, file.workspaceId);
  const isOwner = file.ownerId === userId;
  const share = file.shares.find((s) => s.userId === userId);

  const canView = isOwner || admin || !!share;
  const canEdit = isOwner || admin || share?.role === "EDIT";

  if (!canView) throw new HttpError(403, "You don't have access to this file");
  if (needEdit && !canEdit) throw new HttpError(403, "You don't have permission to manage this file");

  return file;
}

driveRouter.get("/", async (req, res, next) => {
  try {
    const workspaceId = req.query.workspaceId as string;
    const scope = (req.query.scope as string) ?? "mine";
    const folderId = typeof req.query.folderId === "string" && req.query.folderId ? req.query.folderId : null;
    if (!workspaceId) throw new HttpError(400, "workspaceId is required");
    await requireWorkspaceMembership(req.userId!, workspaceId);

    if (folderId) {
      // Confirms the folder actually belongs to this workspace before scoping to it.
      const folder = await prisma.driveFolder.findUnique({ where: { id: folderId } });
      if (!folder || folder.workspaceId !== workspaceId) throw new HttpError(404, "Folder not found");
    }

    let where;
    if (scope === "all") {
      const admin = await isWorkspaceAdmin(req.userId!, workspaceId);
      if (!admin) throw new HttpError(403, "Only super admins can view all files");
      where = { workspaceId, folderId };
    } else if (scope === "shared") {
      where = { workspaceId, folderId, shares: { some: { userId: req.userId! } } };
    } else {
      where = { workspaceId, folderId, ownerId: req.userId! };
    }

    const [files, folders, breadcrumb] = await Promise.all([
      prisma.driveFile.findMany({ where, include: fileInclude, orderBy: { createdAt: "desc" } }),
      prisma.driveFolder.findMany({
        where: { workspaceId, parentId: folderId },
        include: { owner: true, _count: { select: { files: true, children: true } } },
        orderBy: { name: "asc" },
      }),
      folderId ? buildBreadcrumb(folderId) : Promise.resolve([]),
    ]);

    res.json({ files, folders, breadcrumb });
  } catch (err) {
    next(err);
  }
});

const createFolderSchema = z.object({
  workspaceId: z.string().min(1),
  parentId: z.string().min(1).nullable().optional(),
  name: z.string().min(1).max(120),
});

driveRouter.post("/folders", async (req, res, next) => {
  try {
    const data = createFolderSchema.parse(req.body);
    await requireWorkspaceMembership(req.userId!, data.workspaceId);

    if (data.parentId) {
      const parent = await prisma.driveFolder.findUnique({ where: { id: data.parentId } });
      if (!parent || parent.workspaceId !== data.workspaceId) throw new HttpError(404, "Parent folder not found");
    }

    const folder = await prisma.driveFolder.create({
      data: {
        workspaceId: data.workspaceId,
        parentId: data.parentId ?? null,
        ownerId: req.userId!,
        name: data.name,
      },
      include: { owner: true, _count: { select: { files: true, children: true } } },
    });

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

const renameFolderSchema = z.object({ name: z.string().min(1).max(120) });

driveRouter.patch("/folders/:id", async (req, res, next) => {
  try {
    await requireFolderAccess(req.userId!, req.params.id, true);
    const data = renameFolderSchema.parse(req.body);
    const folder = await prisma.driveFolder.update({
      where: { id: req.params.id },
      data: { name: data.name },
      include: { owner: true, _count: { select: { files: true, children: true } } },
    });
    res.json({ folder });
  } catch (err) {
    next(err);
  }
});

driveRouter.delete("/folders/:id", async (req, res, next) => {
  try {
    await requireFolderAccess(req.userId!, req.params.id, true);
    await prisma.driveFolder.delete({ where: { id: req.params.id } });
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

driveRouter.post("/upload", uploadDriveFile.single("file"), async (req, res, next) => {
  try {
    const workspaceId = req.body.workspaceId as string;
    const folderId = (req.body.folderId as string) || null;
    if (!workspaceId) throw new HttpError(400, "workspaceId is required");
    await requireWorkspaceMembership(req.userId!, workspaceId);
    if (!req.file) throw new HttpError(400, "No file uploaded");

    if (folderId) {
      const folder = await prisma.driveFolder.findUnique({ where: { id: folderId } });
      if (!folder || folder.workspaceId !== workspaceId) throw new HttpError(404, "Folder not found");
    }

    const file = await prisma.driveFile.create({
      data: {
        workspaceId,
        folderId,
        ownerId: req.userId!,
        filename: req.file.originalname,
        url: `/uploads/drive/${req.file.filename}`,
        mimeType: req.file.mimetype,
        sizeBytes: req.file.size,
      },
      include: fileInclude,
    });

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

// Creates a brand-new plain-text/markdown document directly in Drive (no upload
// step) — the "New doc" equivalent of Google Drive's blank file creation.
const createTextFileSchema = z.object({
  workspaceId: z.string().min(1),
  folderId: z.string().min(1).nullable().optional(),
  filename: z.string().min(1).max(150),
  content: z.string().max(2_000_000).default(""),
});

driveRouter.post("/text", async (req, res, next) => {
  try {
    const data = createTextFileSchema.parse(req.body);
    await requireWorkspaceMembership(req.userId!, data.workspaceId);

    if (data.folderId) {
      const folder = await prisma.driveFolder.findUnique({ where: { id: data.folderId } });
      if (!folder || folder.workspaceId !== data.workspaceId) throw new HttpError(404, "Folder not found");
    }

    const isMarkdown = /\.md$/i.test(data.filename);
    const filename = /\.(txt|md)$/i.test(data.filename) ? data.filename : `${data.filename}.txt`;
    const diskName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${isMarkdown ? ".md" : ".txt"}`;
    await fs.writeFile(path.join(uploadRoot, "drive", diskName), data.content, "utf-8");

    const file = await prisma.driveFile.create({
      data: {
        workspaceId: data.workspaceId,
        folderId: data.folderId ?? null,
        ownerId: req.userId!,
        filename,
        url: `/uploads/drive/${diskName}`,
        mimeType: isMarkdown ? "text/markdown" : "text/plain",
        sizeBytes: Buffer.byteLength(data.content, "utf-8"),
      },
      include: fileInclude,
    });

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

driveRouter.get("/:id", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id);
    res.json({ file });
  } catch (err) {
    next(err);
  }
});

const updateFileSchema = z.object({
  filename: z.string().min(1).max(150).optional(),
  folderId: z.string().min(1).nullable().optional(),
});

driveRouter.patch("/:id", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id, true);
    const data = updateFileSchema.parse(req.body);

    if (data.folderId) {
      const folder = await prisma.driveFolder.findUnique({ where: { id: data.folderId } });
      if (!folder || folder.workspaceId !== file.workspaceId) throw new HttpError(404, "Folder not found");
    }

    const updated = await prisma.driveFile.update({
      where: { id: file.id },
      data: {
        ...(data.filename !== undefined ? { filename: data.filename } : {}),
        ...(data.folderId !== undefined ? { folderId: data.folderId } : {}),
      },
      include: fileInclude,
    });

    res.json({ file: updated });
  } catch (err) {
    next(err);
  }
});

// Overwrites a text/markdown file's content in place — the "edit in Drive"
// counterpart to createTextFileSchema above, for files already on disk.
const updateContentSchema = z.object({ content: z.string().max(2_000_000) });

driveRouter.put("/:id/content", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id, true);
    if (!file.mimeType || !TEXT_FILE_MIME_TYPES.has(file.mimeType)) {
      throw new HttpError(400, "Only plain text or markdown files can be edited in place");
    }
    const data = updateContentSchema.parse(req.body);

    const diskPath = path.join(uploadRoot, "drive", path.basename(file.url));
    await fs.writeFile(diskPath, data.content, "utf-8");

    const updated = await prisma.driveFile.update({
      where: { id: file.id },
      data: { sizeBytes: Buffer.byteLength(data.content, "utf-8") },
      include: fileInclude,
    });

    res.json({ file: updated });
  } catch (err) {
    next(err);
  }
});

driveRouter.delete("/:id", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id, true);
    await prisma.driveFile.delete({ where: { id: file.id } });
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

const shareSchema = z.object({
  email: z.string().email(),
  role: z.enum(["VIEW", "EDIT"]).default("VIEW"),
});

driveRouter.post("/:id/share", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id, true);
    const data = shareSchema.parse(req.body);

    const user = await prisma.user.findUnique({ where: { email: data.email } });
    if (!user) throw new HttpError(404, "No user found with that email");

    await requireWorkspaceMembership(user.id, file.workspaceId).catch(() => {
      throw new HttpError(400, "That person must be a member of this workspace first");
    });

    const share = await prisma.driveFileShare.upsert({
      where: { fileId_userId: { fileId: file.id, userId: user.id } },
      update: { role: data.role },
      create: { fileId: file.id, userId: user.id, role: data.role },
      include: { user: true },
    });

    await notifyUser({
      recipientId: user.id,
      actorId: req.userId!,
      type: "DRIVE_SHARE",
      message: `"${file.filename}" was shared with you`,
      fileId: file.id,
    });

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

driveRouter.delete("/:id/share/:userId", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id, true);
    await prisma.driveFileShare.delete({
      where: { fileId_userId: { fileId: file.id, userId: req.params.userId } },
    });
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

// ---------- Access log (who viewed/downloaded, and when) ----------

const logAccessSchema = z.object({ action: z.enum(["VIEW", "DOWNLOAD"]) });

driveRouter.post("/:id/access", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id);
    const data = logAccessSchema.parse(req.body);

    await prisma.driveFileAccessLog.create({
      data: { fileId: file.id, userId: req.userId!, action: data.action },
    });

    res.status(201).json({ message: "Logged" });
  } catch (err) {
    next(err);
  }
});

driveRouter.get("/:id/access-log", async (req, res, next) => {
  try {
    const file = await requireFileAccess(req.userId!, req.params.id);
    const logs = await prisma.driveFileAccessLog.findMany({
      where: { fileId: file.id },
      include: { user: true },
      orderBy: { createdAt: "desc" },
      take: 50,
    });
    res.json({ logs });
  } catch (err) {
    next(err);
  }
});
