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

import { requireAdmin } from "./rbac";

function normalizeName(value: string) {
  return value.trim();
}

type AnyCtx = QueryCtx | MutationCtx;

async function ensureUniqueName(ctx: AnyCtx, tenantId: string, name: string, excludeId?: Id<"teams">) {
  const existing = await ctx.db
    .query("teams")
    .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId).eq("name", name))
    .first();
  if (existing && (!excludeId || existing._id !== excludeId)) {
    throw new ConvexError("Já existe um time 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 teams = await ctx.db
      .query("teams")
      .withIndex("by_tenant_name", (q) => q.eq("tenantId", tenantId))
      .take(50);

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

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

    return teams.map((team) => {
      const members = users
        .filter((user) => (user.teams ?? []).includes(team.name))
        .map((user) => ({
          id: user._id,
          name: user.name,
          email: user.email,
          role: user.role ?? "AGENT",
        }));

      const linkedQueues = queues.filter((queue) => queue.teamId === team._id);

      return {
        id: team._id,
        name: team.name,
        description: team.description ?? "",
        members,
        queueCount: linkedQueues.length,
        createdAt: team._creationTime,
      };
    });
  },
});

export const create = mutation({
  args: {
    tenantId: v.string(),
    actorId: v.id("users"),
    name: v.string(),
    description: v.optional(v.string()),
  },
  handler: async (ctx, { tenantId, actorId, name, description }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const trimmed = normalizeName(name);
    if (trimmed.length < 2) {
      throw new ConvexError("Informe um nome válido para o time");
    }
    await ensureUniqueName(ctx, tenantId, trimmed);
    const id = await ctx.db.insert("teams", {
      tenantId,
      name: trimmed,
      description,
    });
    return id;
  },
});

export const update = mutation({
  args: {
    teamId: v.id("teams"),
    tenantId: v.string(),
    actorId: v.id("users"),
    name: v.string(),
    description: v.optional(v.string()),
  },
  handler: async (ctx, { teamId, tenantId, actorId, name, description }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const team = await ctx.db.get(teamId);
    if (!team || team.tenantId !== tenantId) {
      throw new ConvexError("Time não encontrado");
    }
    const trimmed = normalizeName(name);
    if (trimmed.length < 2) {
      throw new ConvexError("Informe um nome válido para o time");
    }
    await ensureUniqueName(ctx, tenantId, trimmed, teamId);

    if (team.name !== trimmed) {
      const users = await ctx.db
        .query("users")
        .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
        .take(50);

      const now = users
        .filter((user) => (user.teams ?? []).includes(team.name))
        .map(async (user) => {
          const teams = (user.teams ?? []).map((entry) => (entry === team.name ? trimmed : entry));
          await ctx.db.patch(user._id, { teams });
        });
      await Promise.all(now);
    }

    await ctx.db.patch(teamId, { name: trimmed, description });
  },
});

export const remove = mutation({
  args: {
    teamId: v.id("teams"),
    tenantId: v.string(),
    actorId: v.id("users"),
  },
  handler: async (ctx, { teamId, tenantId, actorId }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const team = await ctx.db.get(teamId);
    if (!team || team.tenantId !== tenantId) {
      throw new ConvexError("Time não encontrado");
    }

    const queuesLinked = await ctx.db
      .query("queues")
      .withIndex("by_tenant_team", (q) => q.eq("tenantId", tenantId).eq("teamId", teamId))
      .first();
    if (queuesLinked) {
      throw new ConvexError("Remova ou realoque as filas associadas antes de excluir o time");
    }

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

    await Promise.all(
      users
        .filter((user) => (user.teams ?? []).includes(team.name))
        .map((user) => {
          const teams = (user.teams ?? []).filter((entry) => entry !== team.name);
          return ctx.db.patch(user._id, { teams });
        })
    );

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

export const setMembers = mutation({
  args: {
    teamId: v.id("teams"),
    tenantId: v.string(),
    actorId: v.id("users"),
    memberIds: v.array(v.id("users")),
  },
  handler: async (ctx, { teamId, tenantId, actorId, memberIds }) => {
    await requireAdmin(ctx, actorId, tenantId);
    const team = await ctx.db.get(teamId);
    if (!team || team.tenantId !== tenantId) {
      throw new ConvexError("Time não encontrado");
    }

    const users = await ctx.db
      .query("users")
      .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
      .take(50);
    const tenantUserIds = new Set(users.map((user) => user._id));
    for (const memberId of memberIds) {
      if (!tenantUserIds.has(memberId)) {
        throw new ConvexError("Usuário inválido para este tenant");
      }
    }
    const target = new Set(memberIds);

    await Promise.all(
      users.map(async (user) => {
        const teams = new Set(user.teams ?? []);
        const hasTeam = teams.has(team.name);
        const shouldHave = target.has(user._id);

        if (shouldHave && !hasTeam) {
          teams.add(team.name);
          await ctx.db.patch(user._id, { teams: Array.from(teams) });
        }

        if (!shouldHave && hasTeam) {
          teams.delete(team.name);
          await ctx.db.patch(user._id, { teams: Array.from(teams) });
        }
      })
    );
  },
});

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

    return users.map((user) => ({
      id: user._id,
      name: user.name,
      email: user.email,
      role: user.role ?? "AGENT",
      teams: user.teams ?? [],
    }));
  },
});
