import { mutation, query } from "./_generated/server";
import type { MutationCtx, QueryCtx } from "./_generated/server";
import { ConvexError, v } from "convex/values";
import type { Id } from "./_generated/dataModel";

import { requireAdmin, requireStaff } from "./rbac";

type TicketStatusNormalized = "PENDING" | "AWAITING_ATTENDANCE" | "PAUSED" | "RESOLVED";

const STATUS_NORMALIZE_MAP: Record<string, TicketStatusNormalized> = {
  NEW: "PENDING",
  PENDING: "PENDING",
  OPEN: "AWAITING_ATTENDANCE",
  AWAITING_ATTENDANCE: "AWAITING_ATTENDANCE",
  ON_HOLD: "PAUSED",
  PAUSED: "PAUSED",
  RESOLVED: "RESOLVED",
  CLOSED: "RESOLVED",
};

function normalizeStatus(status: string | null | undefined): TicketStatusNormalized {
  if (!status) return "PENDING";
  const normalized = STATUS_NORMALIZE_MAP[status.toUpperCase()];
  return normalized ?? "PENDING";
}

const QUEUE_RENAME_LOOKUP: Record<string, string> = {
  "Suporte N1": "Chamados",
  "suporte-n1": "Chamados",
  chamados: "Chamados",
  "Suporte N2": "Laboratório",
  "suporte-n2": "Laboratório",
  laboratorio: "Laboratório",
  Laboratorio: "Laboratório",
  visitas: "Visitas",
};

function renameQueueString(value: string) {
  const direct = QUEUE_RENAME_LOOKUP[value];
  if (direct) return direct;
  const normalizedKey = value.replace(/\s+/g, "-").toLowerCase();
  return QUEUE_RENAME_LOOKUP[normalizedKey] ?? value;
}

function slugify(value: string) {
  return value
    .normalize("NFD")
    .replace(/[^\w\s-]/g, "")
    .trim()
    .replace(/\s+/g, "-")
    .replace(/-+/g, "-")
    .toLowerCase();
}

type AnyCtx = QueryCtx | MutationCtx;

async function ensureUniqueSlug(ctx: AnyCtx, tenantId: string, slug: string, excludeId?: Id<"queues">) {
  const existing = await ctx.db
    .query("queues")
    .withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId).eq("slug", slug))
    .first();
  if (existing && (!excludeId || existing._id !== excludeId)) {
    throw new ConvexError("Já existe uma fila com este identificador");
  }
}

async function ensureUniqueName(ctx: AnyCtx, tenantId: string, name: string, excludeId?: Id<"queues">) {
  const existing = await ctx.db
    .query("queues")
    .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId).eq("name", name))
    .first();
  if (existing && (!excludeId || existing._id !== excludeId)) {
    throw new ConvexError("Já existe uma fila com este nome");
  }
}

export const list = query({
  args: { tenantId: v.string(), viewerId: v.id("users") },
  handler: async (ctx, { tenantId, viewerId }) => {
    await requireAdmin(ctx, viewerId, tenantId);
    const queues = await ctx.db
      .query("queues")
      .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
      .take(50);

    const teams = await ctx.db
      .query("teams")
      .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
      .take(50);

    return queues.map((queue) => {
      const team = queue.teamId ? teams.find((item) => item._id === queue.teamId) : null;
      return {
        id: queue._id,
        name: queue.name,
        slug: queue.slug,
        team: team
          ? {
              id: team._id,
              name: team.name,
            }
          : null,
      };
    });
  },
});

export const listForStaff = query({
  args: { tenantId: v.string(), viewerId: v.id("users") },
  handler: async (ctx, { tenantId, viewerId }) => {
    await requireStaff(ctx, viewerId, tenantId)
    const queues = await ctx.db
      .query("queues")
      .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
      .take(50)

    const teams = await ctx.db
      .query("teams")
      .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
      .take(50)

    return queues.map((queue) => {
      const team = queue.teamId ? teams.find((item) => item._id === queue.teamId) : null
      return {
        id: queue._id,
        name: queue.name,
        slug: queue.slug,
        team: team
          ? {
              id: team._id,
              name: team.name,
            }
          : null,
      }
    })
  },
})

export const summary = query({
  args: { tenantId: v.string(), viewerId: v.id("users") },
  handler: async (ctx, { tenantId, viewerId }) => {
    await requireStaff(ctx, viewerId, tenantId);
    const queues = await ctx.db.query("queues").withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)).take(50);
    const result = await Promise.all(
      queues.map(async (qItem) => {
        const tickets = await ctx.db
          .query("tickets")
          .withIndex("by_tenant_queue", (q) => q.eq("tenantId", tenantId).eq("queueId", qItem._id))
          .take(50);
        let pending = 0;
        let inProgress = 0;
        let paused = 0;
        let breached = 0;
        const now = Date.now();
        for (const ticket of tickets) {
          const status = normalizeStatus(ticket.status);
          const isWorking = ticket.working === true;
          if (status === "PENDING") {
            pending += 1;
          } else if (status === "AWAITING_ATTENDANCE") {
            // "Em andamento" conta apenas tickets com play ativo
            if (isWorking) {
              inProgress += 1;
            } else {
              // Tickets em atendimento sem play ativo contam como "Em aberto"
              pending += 1;
            }
          } else if (status === "PAUSED") {
            paused += 1;
          }
          if (status !== "RESOLVED") {
            const dueAt = typeof ticket.dueAt === "number" ? ticket.dueAt : null;
            if (dueAt && dueAt < now) {
              breached += 1;
            }
          }
        }
        return {
          id: qItem._id,
          name: renameQueueString(qItem.name),
          pending,
          inProgress,
          paused,
          breached,
        };
      })
    );
    return result;
  },
});

export const create = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    name: v.string(),
    teamId: v.optional(v.id("teams")),
  },
  handler: async (ctx, { tenantId, actorId, name, teamId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const trimmed = name.trim();
    if (trimmed.length < 2) {
      throw new ConvexError("Informe um nome válido para a fila");
    }
    await ensureUniqueName(ctx, tenantId, trimmed);
    const slug = slugify(trimmed);
    await ensureUniqueSlug(ctx, tenantId, slug);
    if (teamId) {
      const team = await ctx.db.get(teamId);
      if (!team || team.tenantId !== tenantId) {
        throw new ConvexError("Time inválido");
      }
    }
    const id = await ctx.db.insert("queues", {
      tenantId,
      name: trimmed,
      slug,
      teamId: teamId ?? undefined,
    });
    return id;
  },
});

export const update = mutation({
  args: {
    queueId: v.id("queues"),
    tenantId: v.string(),
    actorId: v.id("users"),
    name: v.string(),
    teamId: v.optional(v.id("teams")),
  },
  handler: async (ctx, { queueId, tenantId, actorId, name, teamId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const queue = await ctx.db.get(queueId);
    if (!queue || queue.tenantId !== tenantId) {
      throw new ConvexError("Fila não encontrada");
    }
    const trimmed = name.trim();
    if (trimmed.length < 2) {
      throw new ConvexError("Informe um nome válido para a fila");
    }
    await ensureUniqueName(ctx, tenantId, trimmed, queueId);
    let slug = queue.slug;
    if (queue.name !== trimmed) {
      slug = slugify(trimmed);
      await ensureUniqueSlug(ctx, tenantId, slug, queueId);
    }
    if (teamId) {
      const team = await ctx.db.get(teamId);
      if (!team || team.tenantId !== tenantId) {
        throw new ConvexError("Time inválido");
      }
    }
    await ctx.db.patch(queueId, {
      name: trimmed,
      slug,
      teamId: teamId ?? undefined,
    });
  },
});

export const remove = mutation({
  args: {
    queueId: v.id("queues"),
    tenantId: v.string(),
    actorId: v.id("users"),
  },
  handler: async (ctx, { queueId, tenantId, actorId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const queue = await ctx.db.get(queueId);
    if (!queue || queue.tenantId !== tenantId) {
      throw new ConvexError("Fila não encontrada");
    }

    const ticketUsingQueue = await ctx.db
      .query("tickets")
      .withIndex("by_tenant_queue", (q) => q.eq("tenantId", tenantId).eq("queueId", queueId))
      .first();
    if (ticketUsingQueue) {
      throw new ConvexError("Não é possível remover uma fila vinculada a tickets");
    }

    await ctx.db.delete(queueId);
  },
});
