import { Router, type IRouter } from "express";
import { eq, asc, inArray, sql } from "drizzle-orm";
import { db } from "@workspace/db";
import {
  servicesTable,
  serviceCategoriesTable,
  serviceOrdersTable,
  auditLogsTable,
} from "@workspace/db";
import { requireAuth, requireAdmin } from "../middlewares/auth";
import {
  CreateServiceBody,
  UpdateServiceBody,
  GetServiceParams,
  UpdateServiceParams,
  DeleteServiceParams,
} from "@workspace/api-zod";

const router: IRouter = Router();

router.get("/services", requireAuth, async (_req, res): Promise<void> => {
  const services = await db
    .select()
    .from(servicesTable)
    .orderBy(asc(servicesTable.sortOrder), asc(servicesTable.nameEn));

  if (services.length === 0) {
    res.json([]);
    return;
  }

  // Compute orderCount per service so the admin delete dialog can warn
  // before the first click. The lazy 409 fallback in DELETE /services/:id
  // remains as a safety net.
  const serviceIds = services.map((s) => s.id);
  const orderRows = await db
    .select({
      serviceId: serviceOrdersTable.serviceId,
      count: sql<number>`count(*)::int`,
    })
    .from(serviceOrdersTable)
    .where(inArray(serviceOrdersTable.serviceId, serviceIds))
    .groupBy(serviceOrdersTable.serviceId);
  const orderMap = new Map(orderRows.map((r) => [r.serviceId, r.count]));

  res.json(
    services.map((s) => ({
      ...s,
      orderCount: orderMap.get(s.id) ?? 0,
    })),
  );
});

router.post("/services", requireAdmin, async (req, res): Promise<void> => {
  const parsed = CreateServiceBody.safeParse(req.body);
  if (!parsed.success) {
    res.status(400).json({ error: parsed.error.message });
    return;
  }

  const [service] = await db.insert(servicesTable).values(parsed.data).returning();
  res.status(201).json(service);
});

router.get("/service-categories", requireAuth, async (_req, res): Promise<void> => {
  const categories = await db
    .select()
    .from(serviceCategoriesTable)
    .orderBy(asc(serviceCategoriesTable.sortOrder));
  res.json(categories);
});

router.get("/services/:id", requireAuth, async (req, res): Promise<void> => {
  const params = GetServiceParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const [service] = await db
    .select()
    .from(servicesTable)
    .where(eq(servicesTable.id, params.data.id));

  if (!service) {
    res.status(404).json({ error: "Service not found" });
    return;
  }

  res.json(service);
});

router.patch("/services/:id", requireAdmin, async (req, res): Promise<void> => {
  const params = UpdateServiceParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const parsed = UpdateServiceBody.safeParse(req.body);
  if (!parsed.success) {
    res.status(400).json({ error: parsed.error.message });
    return;
  }

  const [service] = await db
    .update(servicesTable)
    .set(parsed.data)
    .where(eq(servicesTable.id, params.data.id))
    .returning();

  if (!service) {
    res.status(404).json({ error: "Service not found" });
    return;
  }

  res.json(service);
});

router.delete("/services/:id", requireAdmin, async (req, res): Promise<void> => {
  const params = DeleteServiceParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const serviceId = params.data.id;

  const [existing] = await db
    .select()
    .from(servicesTable)
    .where(eq(servicesTable.id, serviceId));
  if (!existing) {
    res.status(404).json({ error: "Service not found" });
    return;
  }

  const force = req.query.force === "true" || req.query.force === "1";

  const [orderRow] = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(serviceOrdersTable)
    .where(eq(serviceOrdersTable.serviceId, serviceId));

  const orderCount = orderRow?.count ?? 0;
  const hasDeps = orderCount > 0;

  if (hasDeps && !force) {
    res.status(409).json({
      error: "Service has dependent records",
      orderCount,
    });
    return;
  }

  await db.transaction(async (tx) => {
    if (hasDeps) {
      // service_orders.service_id has ON DELETE RESTRICT, so we must clear
      // dependent orders first before the service itself can be removed.
      await tx
        .delete(serviceOrdersTable)
        .where(eq(serviceOrdersTable.serviceId, serviceId));
    }
    await tx.delete(servicesTable).where(eq(servicesTable.id, serviceId));
  });

  if (hasDeps && force) {
    await db.insert(auditLogsTable).values({
      actorUserId: req.session.userId ?? null,
      action: "service.force_delete",
      targetType: "service",
      targetId: serviceId,
      metadata: {
        nameEn: existing.nameEn,
        orderCount,
      },
    });
  }

  res.sendStatus(204);
});

export default router;
