// src/lib/support/staff-actions.ts — staff (super-admin) support inbox actions (ELEVA-105).
"use server";
import { and, asc, eq, inArray, isNull, ne, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { env } from "@/env";
import { getActiveUserId } from "@/lib/active-org";
import { assertSuperAdmin } from "@/lib/auth/super-admin";
import { user } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";
import { supportTickets } from "@/lib/db/domain-schema";
import type { TicketStatus } from "./types";

/** Inbox filters for the staff space. */
export type StaffInboxFilter = "mine" | "unassigned" | "open";

/** A ticket row as surfaced in the staff inbox (joined creator/assignee). */
export type StaffTicketRow = {
  id: string;
  title: string;
  status: TicketStatus;
  priority: import("./types").TicketPriority;
  createdAt: Date;
  updatedAt: Date;
  orgId: string | null;
  assignedTo: string | null;
  creatorName: string | null;
  creatorEmail: string | null;
  assigneeName: string | null;
};

// Order high → medium → low via a CASE rank, then oldest-first within a priority.
const PRIORITY_RANK = sql`CASE ${supportTickets.priority}
  WHEN 'high' THEN 0
  WHEN 'medium' THEN 1
  WHEN 'low' THEN 2
  ELSE 3 END`;

const filterSchema = z.enum(["mine", "unassigned", "open"]);
const statusSchema = z.enum(["open", "in_progress", "resolved"]);

/**
 * All tickets matching the inbox filter (staff-only). Sorted by priority
 * (high → low) then createdAt ascending (oldest first within a priority).
 * - "open": status != resolved (all active).
 * - "unassigned": active AND assignedTo IS NULL.
 * - "mine": assignedTo = current staff user (active only — resolved excluded).
 */
export async function getStaffInboxAction(
  filter: StaffInboxFilter,
): Promise<StaffTicketRow[]> {
  await assertSuperAdmin();
  const parsed = filterSchema.parse(filter);
  const staffId = await getActiveUserId();

  const notResolved = ne(supportTickets.status, "resolved");
  const where =
    parsed === "unassigned"
      ? and(notResolved, isNull(supportTickets.assignedTo))
      : parsed === "mine"
        ? and(notResolved, eq(supportTickets.assignedTo, staffId))
        : notResolved;

  const assignee = db
    .select({ id: user.id, name: user.name })
    .from(user)
    .as("assignee");

  const rows = await db
    .select({
      id: supportTickets.id,
      title: supportTickets.title,
      status: supportTickets.status,
      priority: supportTickets.priority,
      createdAt: supportTickets.createdAt,
      updatedAt: supportTickets.updatedAt,
      orgId: supportTickets.orgId,
      assignedTo: supportTickets.assignedTo,
      creatorName: user.name,
      creatorEmail: user.email,
      assigneeName: assignee.name,
    })
    .from(supportTickets)
    .leftJoin(user, eq(supportTickets.userId, user.id))
    .leftJoin(assignee, eq(supportTickets.assignedTo, assignee.id))
    .where(where)
    .orderBy(PRIORITY_RANK, asc(supportTickets.createdAt));

  return rows;
}

/**
 * Assign (or unassign) a ticket to a staff user. staffId may be the caller
 * (self-assign), another staff id, or null to unassign. Staff-only. Bumps
 * updatedAt.
 */
export async function assignTicketAction(
  ticketId: string,
  staffId: string | null,
): Promise<void> {
  await assertSuperAdmin();
  await db
    .update(supportTickets)
    .set({ assignedTo: staffId, updatedAt: new Date() })
    .where(eq(supportTickets.id, ticketId));
  revalidatePath("/support/staff");
  revalidatePath(`/support/staff/${ticketId}`);
}

/** Self-assign convenience — assigns the ticket to the current staff user. */
export async function selfAssignAction(ticketId: string): Promise<void> {
  await assertSuperAdmin();
  const staffId = await getActiveUserId();
  await assignTicketAction(ticketId, staffId);
}

/** Update a ticket's status (staff-only). Bumps updatedAt. */
export async function setTicketStatusAction(
  ticketId: string,
  status: TicketStatus,
): Promise<void> {
  await assertSuperAdmin();
  const parsed = statusSchema.parse(status);
  await db
    .update(supportTickets)
    .set({ status: parsed, updatedAt: new Date() })
    .where(eq(supportTickets.id, ticketId));
  revalidatePath("/support/staff");
  revalidatePath(`/support/staff/${ticketId}`);
}

/**
 * The staff users available for assignment. Resolves the SUPER_ADMIN_EMAILS
 * allow-list against the `user` table by email (case-insensitive).
 *
 * Limitation: a super-admin who has never signed in (no `user` row) won't
 * appear here. The assign dropdown only lists staff that exist as users — which
 * is exactly who can act on a ticket, so this is acceptable for the MVP.
 */
export async function listStaffAction(): Promise<
  { id: string; name: string; email: string }[]
> {
  await assertSuperAdmin();
  const emails = (env.SUPER_ADMIN_EMAILS ?? "")
    .split(",")
    .map((e) => e.trim().toLowerCase())
    .filter(Boolean);
  if (emails.length === 0) return [];

  const rows = await db
    .select({ id: user.id, name: user.name, email: user.email })
    .from(user)
    .where(inArray(sql`lower(${user.email})`, emails));

  return rows.map((r) => ({ id: r.id, name: r.name, email: r.email }));
}
