import { Router, type IRouter } from "express";
import { eq, and, desc, sql, isNull, inArray, or } from "drizzle-orm";
import { db } from "@workspace/db";
import {
  serviceOrdersTable,
  servicesTable,
  usersTable,
  notificationsTable,
  userRolesTable,
  rolesTable,
  rolePermissionsTable,
  permissionsTable,
} from "@workspace/db";
import { requireAuth, requirePermission } from "../middlewares/auth";
import {
  CreateServiceOrderBody,
  UpdateServiceOrderStatusParams as ServiceOrderIdParams,
  UpdateServiceOrderStatusBody,
} from "@workspace/api-zod";

const router: IRouter = Router();

const PERM_RECEIVE = "orders.receive";
const ACTIVE_STATUSES = ["pending", "received", "preparing"] as const;
// Server-side undo window for restoring a cancelled order back to its
// prior status. Includes a small buffer over the client UI window
// (7s) to absorb network/clock skew while still making cancellation
// effectively permanent shortly after.
const RESTORE_WINDOW_MS = 15_000;

async function loadOrder(id: number) {
  const [row] = await db
    .select({
      id: serviceOrdersTable.id,
      serviceId: serviceOrdersTable.serviceId,
      userId: serviceOrdersTable.userId,
      assignedTo: serviceOrdersTable.assignedTo,
      notes: serviceOrdersTable.notes,
      status: serviceOrdersTable.status,
      createdAt: serviceOrdersTable.createdAt,
      updatedAt: serviceOrdersTable.updatedAt,
      service: {
        id: servicesTable.id,
        nameAr: servicesTable.nameAr,
        nameEn: servicesTable.nameEn,
        imageUrl: servicesTable.imageUrl,
      },
    })
    .from(serviceOrdersTable)
    .innerJoin(servicesTable, eq(serviceOrdersTable.serviceId, servicesTable.id))
    .where(eq(serviceOrdersTable.id, id));

  if (!row) return null;

  const userIds = [row.userId, ...(row.assignedTo ? [row.assignedTo] : [])];
  const userRows = await db
    .select({
      id: usersTable.id,
      username: usersTable.username,
      displayNameAr: usersTable.displayNameAr,
      displayNameEn: usersTable.displayNameEn,
      avatarUrl: usersTable.avatarUrl,
    })
    .from(usersTable)
    .where(inArray(usersTable.id, userIds));

  const userById = new Map(userRows.map((u) => [u.id, u]));
  return {
    ...row,
    requester: userById.get(row.userId) ?? null,
    assignee: row.assignedTo ? (userById.get(row.assignedTo) ?? null) : null,
  };
}

async function getReceiverUserIds(): Promise<number[]> {
  const rows = await db
    .selectDistinct({ userId: userRolesTable.userId })
    .from(userRolesTable)
    .innerJoin(rolePermissionsTable, eq(rolePermissionsTable.roleId, userRolesTable.roleId))
    .innerJoin(permissionsTable, eq(rolePermissionsTable.permissionId, permissionsTable.id))
    .where(eq(permissionsTable.name, PERM_RECEIVE));
  return rows.map((r) => r.userId);
}

async function isAdmin(userId: number): Promise<boolean> {
  const rows = await db
    .select({ name: rolesTable.name })
    .from(userRolesTable)
    .innerJoin(rolesTable, eq(userRolesTable.roleId, rolesTable.id))
    .where(and(eq(userRolesTable.userId, userId), eq(rolesTable.name, "admin")));
  return rows.length > 0;
}

async function emitToUser(userId: number, event: string, payload?: unknown) {
  const { io } = await import("../index.js");
  io.to(`user:${userId}`).emit(event, payload);
}

async function notifyUser(
  userId: number,
  titleAr: string,
  titleEn: string,
  bodyAr: string,
  bodyEn: string,
  orderId: number,
  actorId?: number,
) {
  // Never notify users about actions they themselves initiated.
  if (actorId !== undefined && actorId === userId) return;
  const [notification] = await db
    .insert(notificationsTable)
    .values({
      userId,
      titleAr,
      titleEn,
      bodyAr,
      bodyEn,
      type: "order",
      relatedId: orderId,
      relatedType: "order",
    })
    .returning();
  await emitToUser(userId, "notification_created", notification);
}

async function broadcastIncomingChanged(receiverIds: number[]) {
  for (const uid of receiverIds) {
    await emitToUser(uid, "order_incoming_changed");
  }
}

// POST /api/orders — place a new order
router.post("/orders", requireAuth, async (req, res): Promise<void> => {
  const userId = req.session.userId!;
  const parsed = CreateServiceOrderBody.safeParse(req.body);
  if (!parsed.success) {
    res.status(400).json({ error: parsed.error.message });
    return;
  }

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

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

  const [order] = await db
    .insert(serviceOrdersTable)
    .values({
      serviceId: parsed.data.serviceId,
      userId,
      notes: parsed.data.notes ?? null,
    })
    .returning();

  const enriched = await loadOrder(order.id);
  const receivers = await getReceiverUserIds();
  for (const rid of receivers) {
    await notifyUser(
      rid,
      "طلب جديد",
      "New order",
      service.nameAr,
      service.nameEn,
      order.id,
      userId,
    );
  }
  await broadcastIncomingChanged(receivers);

  res.status(201).json(enriched);
});

// GET /api/orders/my
router.get("/orders/my", requireAuth, async (req, res): Promise<void> => {
  const userId = req.session.userId!;
  const rows = await db
    .select({ id: serviceOrdersTable.id })
    .from(serviceOrdersTable)
    .where(eq(serviceOrdersTable.userId, userId))
    .orderBy(desc(serviceOrdersTable.createdAt))
    .limit(100);
  const enriched = await Promise.all(rows.map((r) => loadOrder(r.id)));
  res.json(enriched.filter(Boolean));
});

// GET /api/orders/incoming — receivers only
router.get(
  "/orders/incoming",
  requireAuth,
  requirePermission(PERM_RECEIVE),
  async (req, res): Promise<void> => {
    const userId = req.session.userId!;
    const rows = await db
      .select({ id: serviceOrdersTable.id })
      .from(serviceOrdersTable)
      .where(
        or(
          and(
            eq(serviceOrdersTable.status, "pending"),
            isNull(serviceOrdersTable.assignedTo),
          ),
          and(
            eq(serviceOrdersTable.assignedTo, userId),
            inArray(serviceOrdersTable.status, ACTIVE_STATUSES as unknown as string[]),
          ),
        ),
      )
      .orderBy(desc(serviceOrdersTable.createdAt))
      .limit(200);
    const enriched = await Promise.all(rows.map((r) => loadOrder(r.id)));
    res.json(enriched.filter(Boolean));
  },
);

// PATCH /api/orders/:id/confirm-receipt — receiver atomic claim
router.patch(
  "/orders/:id/confirm-receipt",
  requireAuth,
  requirePermission(PERM_RECEIVE),
  async (req, res): Promise<void> => {
    const params = ServiceOrderIdParams.safeParse(req.params);
    if (!params.success) {
      res.status(400).json({ error: params.error.message });
      return;
    }
    const userId = req.session.userId!;
    const orderId = params.data.id;

    const [updated] = await db
      .update(serviceOrdersTable)
      .set({ status: "received", assignedTo: userId, updatedAt: new Date() })
      .where(
        and(
          eq(serviceOrdersTable.id, orderId),
          eq(serviceOrdersTable.status, "pending"),
          isNull(serviceOrdersTable.assignedTo),
        ),
      )
      .returning();

    if (!updated) {
      // Distinguish: was it claimed by someone else, or is it no longer claimable
      // (cancelled / completed / already in a terminal state)?
      const current = await db
        .select({ status: serviceOrdersTable.status })
        .from(serviceOrdersTable)
        .where(eq(serviceOrdersTable.id, orderId));
      const currentStatus = current[0]?.status;
      if (!currentStatus || currentStatus === "cancelled" || currentStatus === "completed") {
        res.status(409).json({ error: "order_unavailable" });
      } else {
        res.status(409).json({ error: "already_claimed" });
      }
      return;
    }

    const enriched = await loadOrder(orderId);
    if (enriched) {
      await notifyUser(
        enriched.userId,
        "تم استلام طلبك",
        "Your order was received",
        enriched.service.nameAr,
        enriched.service.nameEn,
        orderId,
        userId,
      );
      await emitToUser(enriched.userId, "order_updated", enriched);
    }
    const receivers = await getReceiverUserIds();
    await broadcastIncomingChanged(receivers);

    res.json(enriched);
  },
);

// PATCH /api/orders/:id/status
router.patch(
  "/orders/:id/status",
  requireAuth,
  async (req, res): Promise<void> => {
    const params = ServiceOrderIdParams.safeParse(req.params);
    if (!params.success) {
      res.status(400).json({ error: params.error.message });
      return;
    }
    const parsed = UpdateServiceOrderStatusBody.safeParse(req.body);
    if (!parsed.success) {
      res.status(400).json({ error: parsed.error.message });
      return;
    }
    const userId = req.session.userId!;
    const orderId = params.data.id;
    const next = parsed.data.status;

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

    const admin = await isAdmin(userId);

    const isRestoreFromCancelled =
      existing.status === "cancelled" && next !== "cancelled";

    if (isRestoreFromCancelled) {
      // Restore a cancelled order back to a prior active status (Undo).
      // Owner/admin may restore to pending or received; the original
      // assignee may restore to received or preparing.
      const isOwner = existing.userId === userId;
      const isAssignee = existing.assignedTo === userId;
      const canOwnerRestore =
        (isOwner || admin) && (next === "pending" || next === "received");
      const canAssigneeRestore =
        isAssignee && (next === "received" || next === "preparing");
      if (!canOwnerRestore && !canAssigneeRestore) {
        if (!isOwner && !isAssignee && !admin) {
          res.status(403).json({ error: "Forbidden" });
          return;
        }
        res.status(400).json({ error: "invalid_transition" });
        return;
      }
      if (next === "pending" && existing.assignedTo !== null) {
        res.status(400).json({ error: "invalid_transition" });
        return;
      }
      if (
        (next === "received" || next === "preparing") &&
        existing.assignedTo === null
      ) {
        res.status(400).json({ error: "invalid_transition" });
        return;
      }
      // Enforce the undo window: once the window expires, the
      // cancellation is permanent. Admin is also bound by this so
      // restore is always Undo, never an arbitrary reopen.
      const cancelledAt = existing.updatedAt
        ? new Date(existing.updatedAt).getTime()
        : 0;
      if (Date.now() - cancelledAt > RESTORE_WINDOW_MS) {
        res.status(400).json({ error: "undo_window_expired" });
        return;
      }
    } else if (next === "preparing" || next === "completed") {
      // If the order is already terminal (cancelled or completed by someone
      // else), surface a specific error so the receiver UI can show the right
      // message and remove the stale card.
      if (existing.status === "cancelled" || existing.status === "completed") {
        res.status(409).json({ error: "order_unavailable" });
        return;
      }
      // Only the assigned receiver or admin
      const isAssignee = existing.assignedTo === userId;
      if (!isAssignee && !admin) {
        res.status(403).json({ error: "Forbidden" });
        return;
      }
      if (next === "preparing" && existing.status !== "received") {
        res.status(400).json({ error: "invalid_transition" });
        return;
      }
      if (next === "completed" && existing.status !== "preparing") {
        res.status(400).json({ error: "invalid_transition" });
        return;
      }
    } else if (next === "pending" || next === "received") {
      // Restore is handled above; outside cancelled, these targets are
      // never valid via this endpoint.
      res.status(400).json({ error: "invalid_transition" });
      return;
    } else if (next === "cancelled") {
      // Check terminal state first so the receiver sees the right message
      // regardless of permission gating order. Admin may still cancel a
      // completed/cancelled order (e.g. corrective action), so they bypass.
      if (
        !admin &&
        (existing.status === "cancelled" || existing.status === "completed")
      ) {
        res.status(409).json({ error: "order_unavailable" });
        return;
      }
      const isOwner = existing.userId === userId;
      const isAssignee = existing.assignedTo === userId;
      const cancellableByOwner =
        existing.status === "pending" || existing.status === "received";
      const cancellableByAssignee =
        existing.status === "received" || existing.status === "preparing";
      if (
        !admin &&
        !(isOwner && cancellableByOwner) &&
        !(isAssignee && cancellableByAssignee)
      ) {
        res.status(403).json({ error: "Forbidden" });
        return;
      }
    } else {
      res.status(400).json({ error: "invalid_transition" });
      return;
    }

    await db
      .update(serviceOrdersTable)
      .set({ status: next, updatedAt: new Date() })
      .where(eq(serviceOrdersTable.id, orderId));

    const enriched = await loadOrder(orderId);

    // Notify owner on every status change — notifyUser will skip if the
    // owner themselves initiated the action (e.g. owner-cancel, or an
    // assignee transition where the owner also holds the receiver role).
    if (enriched) {
      const titleMap: Record<string, [string, string]> = {
        pending: ["تمت استعادة طلبك", "Your order was restored"],
        received: ["تمت استعادة طلبك", "Your order was restored"],
        preparing: ["طلبك قيد التحضير", "Your order is being prepared"],
        completed: ["تم إكمال طلبك", "Your order is completed"],
        cancelled: ["تم إلغاء طلبك", "Your order was cancelled"],
      };
      const [titleAr, titleEn] = titleMap[next];
      let bodyAr = enriched.service.nameAr;
      let bodyEn = enriched.service.nameEn;
      // Distinguish receiver-initiated cancel from admin-initiated cancel
      if (next === "cancelled" && existing.assignedTo === userId) {
        bodyAr = `${enriched.service.nameAr} — استلم طلبك ولكن تم إلغاؤه لاحقاً`;
        bodyEn = `${enriched.service.nameEn} — Your order was claimed but later cancelled by the receiver`;
      }
      await notifyUser(
        enriched.userId,
        titleAr,
        titleEn,
        bodyAr,
        bodyEn,
        orderId,
        userId,
      );
    }
    if (enriched) await emitToUser(enriched.userId, "order_updated", enriched);

    const receivers = await getReceiverUserIds();
    await broadcastIncomingChanged(receivers);
    if (existing.assignedTo) {
      await emitToUser(existing.assignedTo, "order_incoming_changed");
    }

    res.json(enriched);
  },
);

// DELETE /api/orders/:id — owner can delete terminal orders; admin can delete any
router.delete(
  "/orders/:id",
  requireAuth,
  async (req, res): Promise<void> => {
    const params = ServiceOrderIdParams.safeParse(req.params);
    if (!params.success) {
      res.status(400).json({ error: params.error.message });
      return;
    }
    const userId = req.session.userId!;
    const orderId = params.data.id;

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

    const admin = await isAdmin(userId);
    const isOwner = existing.userId === userId;
    const isTerminal =
      existing.status === "completed" || existing.status === "cancelled";

    if (!admin && !(isOwner && isTerminal)) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }

    await db
      .delete(serviceOrdersTable)
      .where(eq(serviceOrdersTable.id, orderId));

    await emitToUser(existing.userId, "order_deleted", { id: orderId });
    if (existing.assignedTo && existing.assignedTo !== existing.userId) {
      await emitToUser(existing.assignedTo, "order_deleted", { id: orderId });
    }
    // If the deleted order was visible to receivers (pending or active),
    // refresh their incoming queues.
    if (
      existing.status === "pending" ||
      existing.status === "received" ||
      existing.status === "preparing"
    ) {
      const receivers = await getReceiverUserIds();
      await broadcastIncomingChanged(receivers);
    }

    res.status(204).end();
  },
);

export default router;
