import fs from "node:fs/promises";
import path from "node:path";
import { FastifyInstance } from "fastify";
import { z } from "zod";
import { db } from "./db.js";
import { requireAuth } from "./auth.js";
import { folderPermissions, getFolder, requireFolderWrite } from "./permissions.js";
import { storage } from "./storage.js";
import { FolderRow } from "./types.js";
import { assertValidFolderName, ensureDir, removeIfExists, safeStoragePath, toFolderStorageRelative } from "./utils.js";

const createSchema = z.object({
  parentId: z.number().int().nullable().optional(),
  name: z.string()
});

const updateSchema = z.object({
  name: z.string()
});

function folderPayload(request: { userInfo: { id: number; username: string; role: "admin" | "user" } }, row: FolderRow) {
  return {
    ...row,
    permissions: folderPermissions(request.userInfo, row)
  };
}

function buildFolderNodes(rows: FolderRow[], userInfo: { id: number; username: string; role: "admin" | "user" }) {
  return rows.map((row) => ({
    id: String(row.id),
    text: `${displayFolderName(row)}(${row.model_count ?? 0})`,
    parentId: row.parent_id,
    path: row.path,
    data: {
      permissions: folderPermissions(userInfo, row),
      libraryType: row.library_type,
      ownerUserId: row.owner_user_id,
      isSystem: Boolean(row.is_system)
    },
    state: {
      opened: row.library_type === "system" && row.parent_id === null
    },
    children: [] as unknown[]
  }));
}

function buildTree(rows: FolderRow[], userInfo: { id: number; username: string; role: "admin" | "user" }) {
  const nodes = buildFolderNodes(rows, userInfo);
  const userGroupNode = {
    id: "virtual:user-libraries",
    text: "用户模型库",
    parentId: null as number | null,
    path: "users",
    data: {
      permissions: { read: true, write: false },
      virtual: true
    },
    state: {
      opened: true
    },
    children: [] as unknown[]
  };
  const byId = new Map(nodes.map((node) => [Number(node.id), node]));
  const roots = [];
  for (const node of nodes) {
    if (node.parentId && byId.has(node.parentId)) {
      byId.get(node.parentId)!.children.push(node);
    } else if (node.data.libraryType === "user") {
      userGroupNode.children.push(node);
    } else {
      roots.push(node);
    }
  }
  if (userGroupNode.children.length > 0) {
    roots.push(userGroupNode);
  }
  return roots;
}

function childPath(parent: FolderRow | undefined, name: string) {
  return parent ? path.posix.join(parent.path, name) : name;
}

function assertCanRenameRoot(folder: FolderRow) {
  if (folder.is_system) {
    throw new Error("系统保留目录不能重命名");
  }
}

function displayFolderName(row: FolderRow) {
  if (row.library_type === "user" && row.is_system && row.owner_username) {
    return row.owner_username;
  }
  return row.name;
}

function ensureUserLibrary(user: { id: number; username: string }) {
  const pathValue = `users/${user.id}`;
  const existing = db.prepare("SELECT id FROM folders WHERE path = ?").get(pathValue) as { id: number } | undefined;
  if (existing) return existing.id;
  const result = db.prepare(`
    INSERT INTO folders (parent_id, name, path, library_type, owner_user_id, is_system)
    VALUES (NULL, ?, ?, 'user', ?, 1)
  `).run("用户模型库", pathValue, user.id);
  return Number(result.lastInsertRowid);
}

export async function folderRoutes(app: FastifyInstance) {
  app.get("/api/folders", { preHandler: [requireAuth] }, async (request) => {
    ensureUserLibrary(request.userInfo);
    const rows = db.prepare(`
      SELECT
        f.*,
        u.username AS owner_username,
        COUNT(m.id) AS model_count
      FROM folders f
      LEFT JOIN users u ON u.id = f.owner_user_id
      LEFT JOIN models m ON m.folder_id = f.id
      GROUP BY f.id
      ORDER BY
        CASE f.library_type WHEN 'system' THEN 0 ELSE 1 END,
        f.owner_user_id,
        f.parent_id,
        f.name
    `).all() as unknown as FolderRow[];
    return { folders: rows.map((row) => folderPayload(request, row)), tree: buildTree(rows, request.userInfo) };
  });

  app.post("/api/folders", { preHandler: [requireAuth] }, async (request, reply) => {
    const body = createSchema.parse(request.body);
    const name = assertValidFolderName(body.name);
    const parent = body.parentId ? getFolder(body.parentId) : undefined;
    if (body.parentId && !parent) {
      return reply.code(404).send({ message: "父目录不存在" });
    }
    if (!parent) {
      return reply.code(400).send({ message: "请选择父目录" });
    }
    if (!requireFolderWrite(request, reply, parent)) return;
    const folderPath = childPath(parent, name);
    try {
      const result = db.prepare(`
        INSERT INTO folders (parent_id, name, path, library_type, owner_user_id, is_system)
        VALUES (?, ?, ?, ?, ?, 0)
      `).run(parent.id, name, folderPath, parent.library_type, parent.owner_user_id);
      await ensureDir(safeStoragePath(toFolderStorageRelative(folderPath)));
      return reply.code(201).send({ id: result.lastInsertRowid, name, path: folderPath });
    } catch {
      return reply.code(409).send({ message: "同级目录已存在" });
    }
  });

  app.put("/api/folders/:id", { preHandler: [requireAuth] }, async (request, reply) => {
    const params = z.object({ id: z.coerce.number().int() }).parse(request.params);
    const body = updateSchema.parse(request.body);
    const name = assertValidFolderName(body.name);
    const folder = getFolder(params.id);
    if (!folder) {
      return reply.code(404).send({ message: "目录不存在" });
    }
    if (!requireFolderWrite(request, reply, folder)) return;
    assertCanRenameRoot(folder);
    const parent = folder.parent_id ? getFolder(folder.parent_id) : undefined;
    const oldPath = folder.path;
    const newPath = childPath(parent, name);
    if (storage.provider !== "local") {
      const modelCount = db.prepare("SELECT COUNT(*) AS count FROM models WHERE file_path LIKE ?")
        .get(`models/${oldPath}/%`) as { count: number };
      if (modelCount.count > 0) {
        return reply.code(400).send({ message: "当前存储模式下，包含模型的目录不能重命名" });
      }
    }

  const tx = () => {
    db.exec("BEGIN");
    try {
      db.prepare("UPDATE folders SET name = ?, path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
        .run(name, newPath, folder.id);

      const descendants = db.prepare("SELECT * FROM folders WHERE path LIKE ? ORDER BY LENGTH(path)")
        .all(`${oldPath}/%`) as unknown as FolderRow[];
      for (const item of descendants) {
        const replacedPath = item.path.replace(oldPath + "/", newPath + "/");
        db.prepare("UPDATE folders SET path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
          .run(replacedPath, item.id);
      }

      if (storage.provider === "local") {
        const models = db.prepare("SELECT id, file_path FROM models WHERE file_path LIKE ?")
          .all(`models/${oldPath}/%`) as { id: number; file_path: string }[];
        for (const model of models) {
          db.prepare("UPDATE models SET file_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
            .run(model.file_path.replace(`models/${oldPath}/`, `models/${newPath}/`), model.id);
        }
      }
      db.exec("COMMIT");
    } catch (error) {
      db.exec("ROLLBACK");
      throw error;
    }
  };

    try {
      tx();
      if (storage.provider === "local") {
        await fs.rename(
          safeStoragePath(toFolderStorageRelative(oldPath)),
          safeStoragePath(toFolderStorageRelative(newPath))
        );
      }
      return { ok: true };
    } catch {
      return reply.code(409).send({ message: "目录重命名失败，可能存在重名目录" });
    }
  });

  app.delete("/api/folders/:id", { preHandler: [requireAuth] }, async (request, reply) => {
    const params = z.object({ id: z.coerce.number().int() }).parse(request.params);
    const folder = getFolder(params.id);
    if (!folder) {
      return reply.code(404).send({ message: "目录不存在" });
    }
    if (!requireFolderWrite(request, reply, folder)) return;
    if (folder.is_system) {
      return reply.code(400).send({ message: "系统保留目录不能删除" });
    }
    db.prepare("DELETE FROM folders WHERE id = ?").run(folder.id);
    await removeIfExists(safeStoragePath(toFolderStorageRelative(folder.path)));
    return { ok: true };
  });
}
