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

const INTERNAL_STAFF_ROLES = new Set(["ADMIN", "AGENT"]);
const CUSTOMER_ROLES = new Set(["COLLABORATOR", "MANAGER"]);

export const ensureUser = mutation({
  args: {
    tenantId: v.string(),
    email: v.string(),
    name: v.string(),
    avatarUrl: v.optional(v.string()),
    role: v.optional(v.string()),
    teams: v.optional(v.array(v.string())),
    companyId: v.optional(v.id("companies")),
    jobTitle: v.optional(v.string()),
    managerId: v.optional(v.id("users")),
  },
  handler: async (ctx, args) => {
    const existing = await ctx.db
      .query("users")
      .withIndex("by_tenant_email", (q) => q.eq("tenantId", args.tenantId).eq("email", args.email))
      .first();
    const reconcile = async (record: typeof existing) => {
      if (!record) return null;
      const hasJobTitleArg = Object.prototype.hasOwnProperty.call(args, "jobTitle");
      const hasManagerArg = Object.prototype.hasOwnProperty.call(args, "managerId");
      const jobTitleChanged = hasJobTitleArg ? (record.jobTitle ?? null) !== (args.jobTitle ?? null) : false;
      const managerChanged = hasManagerArg
        ? String(record.managerId ?? "") !== String(args.managerId ?? "")
        : false;
      const shouldPatch =
        record.tenantId !== args.tenantId ||
        (args.role && record.role !== args.role) ||
        (args.avatarUrl && record.avatarUrl !== args.avatarUrl) ||
        record.name !== args.name ||
        (args.teams && JSON.stringify(args.teams) !== JSON.stringify(record.teams ?? [])) ||
        (args.companyId && record.companyId !== args.companyId) ||
        jobTitleChanged ||
        managerChanged;

      if (shouldPatch) {
        const patch: Record<string, unknown> = {
          tenantId: args.tenantId,
          role: args.role ?? record.role,
          avatarUrl: args.avatarUrl ?? record.avatarUrl,
          name: args.name,
          teams: args.teams ?? record.teams,
          companyId: args.companyId ?? record.companyId,
        };
        if (hasJobTitleArg) {
          patch.jobTitle = args.jobTitle ?? undefined;
        }
        if (hasManagerArg) {
          patch.managerId = args.managerId ?? undefined;
        }
        await ctx.db.patch(record._id, patch);
        const updated = await ctx.db.get(record._id);
        if (updated) {
          return updated;
        }
      }
      return record;
    };

    if (existing) {
      const reconciled = await reconcile(existing);
      if (reconciled) {
        return reconciled;
      }
    } else {
      // Busca por email em todos os tenants (usando limite para evitar OOM)
      // Nota: isso e ineficiente sem indice global por email
      const users = await ctx.db.query("users").take(5000);
      const anyTenant = users.find((user) => user.email === args.email);
      if (anyTenant) {
        const reconciled = await reconcile(anyTenant);
        if (reconciled) {
          return reconciled;
        }
      }
    }
    const id = await ctx.db.insert("users", {
      tenantId: args.tenantId,
      email: args.email,
      name: args.name,
      avatarUrl: args.avatarUrl,
      role: args.role ?? "AGENT",
      teams: args.teams ?? [],
      companyId: args.companyId,
      jobTitle: args.jobTitle,
      managerId: args.managerId,
    });
    return await ctx.db.get(id);
  },
});

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

    // Only internal staff (ADMIN/AGENT) should appear as responsáveis
    return users
      .filter((user) => INTERNAL_STAFF_ROLES.has((user.role ?? "AGENT").toUpperCase()))
      .sort((a, b) => a.name.localeCompare(b.name, "pt-BR"));
  },
});

export const listCustomers = query({
  args: { tenantId: v.string(), viewerId: v.id("users") },
  handler: async (ctx, { tenantId, viewerId }) => {
    const viewer = await requireStaff(ctx, viewerId, tenantId)
    const viewerRole = (viewer.role ?? "AGENT").toUpperCase()
    let managerCompanyId: Id<"companies"> | null = null
    if (viewerRole === "MANAGER") {
      managerCompanyId = viewer.user.companyId ?? null
      if (!managerCompanyId) {
        throw new ConvexError("Gestor não possui empresa vinculada")
      }
    }

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

    const allowed = users.filter((user) => {
      const role = (user.role ?? "COLLABORATOR").toUpperCase()
      if (!CUSTOMER_ROLES.has(role)) return false
      if (managerCompanyId && user.companyId !== managerCompanyId) return false
      return true
    })

    const companyIds = Array.from(
      new Set(
        allowed
          .map((user) => user.companyId)
          .filter((companyId): companyId is Id<"companies"> => Boolean(companyId))
      )
    )

    const companyMap = new Map<string, { name: string; isAvulso?: boolean | null }>()
    if (companyIds.length > 0) {
      await Promise.all(
        companyIds.map(async (companyId) => {
          const company = await ctx.db.get(companyId)
          if (company) {
            companyMap.set(String(companyId), {
              name: company.name,
              isAvulso: company.isAvulso ?? undefined,
            })
          }
        })
      )
    }

    return allowed
      .map((user) => {
        const companyId = user.companyId ? String(user.companyId) : null
        const company = companyId ? companyMap.get(companyId) ?? null : null
        return {
          id: String(user._id),
          name: user.name,
          email: user.email,
          role: (user.role ?? "COLLABORATOR").toUpperCase(),
          companyId,
          companyName: company?.name ?? null,
          companyIsAvulso: Boolean(company?.isAvulso),
          avatarUrl: user.avatarUrl ?? null,
          jobTitle: user.jobTitle ?? null,
          managerId: user.managerId ? String(user.managerId) : null,
        }
      })
      .sort((a, b) => a.name.localeCompare(b.name ?? "", "pt-BR"))
  },
})


export const findByEmail = query({
  args: { tenantId: v.string(), email: v.string() },
  handler: async (ctx, { tenantId, email }) => {
    const record = await ctx.db
      .query("users")
      .withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", email))
      .first();
    return record ?? null;
  },
});

export const deleteUser = mutation({
  args: { userId: v.id("users"), actorId: v.id("users") },
  handler: async (ctx, { userId, actorId }) => {
    const user = await ctx.db.get(userId);
    if (!user) {
      return { status: "not_found" };
    }

    await requireAdmin(ctx, actorId, user.tenantId);

    const assignedTickets = await ctx.db
      .query("tickets")
      .withIndex("by_tenant_assignee", (q) => q.eq("tenantId", user.tenantId).eq("assigneeId", userId))
      .take(1);

    if (assignedTickets.length > 0) {
      throw new ConvexError("Usuário ainda está atribuído a tickets");
    }

    const comments = await ctx.db
      .query("ticketComments")
      .withIndex("by_author", (q) => q.eq("authorId", userId))
      .take(10000);
    if (comments.length > 0) {
      const authorSnapshot = {
        name: user.name,
        email: user.email,
        avatarUrl: user.avatarUrl ?? undefined,
        teams: user.teams ?? undefined,
      };
      await Promise.all(
        comments.map(async (comment) => {
          const existingSnapshot = comment.authorSnapshot;
          const shouldUpdate =
            !existingSnapshot ||
            existingSnapshot.name !== authorSnapshot.name ||
            existingSnapshot.email !== authorSnapshot.email ||
            existingSnapshot.avatarUrl !== authorSnapshot.avatarUrl ||
            JSON.stringify(existingSnapshot.teams ?? []) !== JSON.stringify(authorSnapshot.teams ?? []);
          if (shouldUpdate) {
            await ctx.db.patch(comment._id, { authorSnapshot });
          }
        }),
      );
    }

    // Preserve requester snapshot on tickets where this user is the requester
    const requesterTickets = await ctx.db
      .query("tickets")
      .withIndex("by_tenant_requester", (q) => q.eq("tenantId", user.tenantId).eq("requesterId", userId))
      .take(10000);
    if (requesterTickets.length > 0) {
      const requesterSnapshot = {
        name: user.name,
        email: user.email,
        avatarUrl: user.avatarUrl ?? undefined,
        teams: user.teams ?? undefined,
      };
      for (const t of requesterTickets) {
        const needsPatch = !t.requesterSnapshot ||
          t.requesterSnapshot.name !== requesterSnapshot.name ||
          t.requesterSnapshot.email !== requesterSnapshot.email ||
          t.requesterSnapshot.avatarUrl !== requesterSnapshot.avatarUrl ||
          JSON.stringify(t.requesterSnapshot.teams ?? []) !== JSON.stringify(requesterSnapshot.teams ?? []);
        if (needsPatch) {
          await ctx.db.patch(t._id, { requesterSnapshot });
        }
      }
    }

    // Limpa vínculo de subordinados
    const directReports = await ctx.db
      .query("users")
      .withIndex("by_tenant_manager", (q) => q.eq("tenantId", user.tenantId).eq("managerId", userId))
      .take(1000);
    await Promise.all(
      directReports.map(async (report) => {
        await ctx.db.patch(report._id, { managerId: undefined });
      })
    );

    await ctx.db.delete(userId);
    return { status: "deleted" };
  },
});

/**
 * Atualiza o avatar de um usuário.
 * Passa avatarUrl como null para remover o avatar.
 * Também atualiza os snapshots em comentários e tickets.
 */
export const updateAvatar = mutation({
  args: {
    tenantId: v.string(),
    email: v.string(),
    avatarUrl: v.union(v.string(), v.null()),
  },
  handler: async (ctx, { tenantId, email, avatarUrl }) => {
    const user = await ctx.db
      .query("users")
      .withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", email))
      .first()

    if (!user) {
      return { status: "not_found" }
    }

    // Atualiza o avatar do usuário - usa undefined para remover o campo
    const normalizedAvatarUrl = avatarUrl ?? undefined
    await ctx.db.patch(user._id, { avatarUrl: normalizedAvatarUrl })

    // Cria snapshot base sem avatarUrl se for undefined
    // Isso garante que o campo seja realmente removido do snapshot
    const baseSnapshot: { name: string; email: string; avatarUrl?: string; teams?: string[] } = {
      name: user.name,
      email: user.email,
    }
    if (normalizedAvatarUrl !== undefined) {
      baseSnapshot.avatarUrl = normalizedAvatarUrl
    }
    if (user.teams && user.teams.length > 0) {
      baseSnapshot.teams = user.teams
    }

    // Atualiza snapshots em comentários
    const comments = await ctx.db
      .query("ticketComments")
      .withIndex("by_author", (q) => q.eq("authorId", user._id))
      .take(10000)

    if (comments.length > 0) {
      await Promise.all(
        comments.map(async (comment) => {
          await ctx.db.patch(comment._id, { authorSnapshot: baseSnapshot })
        }),
      )
    }

    // Atualiza snapshots de requester em tickets
    const requesterTickets = await ctx.db
      .query("tickets")
      .withIndex("by_tenant_requester", (q) => q.eq("tenantId", user.tenantId).eq("requesterId", user._id))
      .take(10000)

    if (requesterTickets.length > 0) {
      for (const t of requesterTickets) {
        await ctx.db.patch(t._id, { requesterSnapshot: baseSnapshot })
      }
    }

    // Atualiza snapshots de assignee em tickets
    const assigneeTickets = await ctx.db
      .query("tickets")
      .withIndex("by_tenant_assignee", (q) => q.eq("tenantId", user.tenantId).eq("assigneeId", user._id))
      .take(10000)

    if (assigneeTickets.length > 0) {
      for (const t of assigneeTickets) {
        await ctx.db.patch(t._id, { assigneeSnapshot: baseSnapshot })
      }
    }

    return { status: "updated", avatarUrl: normalizedAvatarUrl }
  },
})

export const assignCompany = mutation({
  args: { tenantId: v.string(), email: v.string(), companyId: v.id("companies"), actorId: v.id("users") },
  handler: async (ctx, { tenantId, email, companyId, actorId }) => {
    await requireAdmin(ctx, actorId, tenantId)
    const user = await ctx.db
      .query("users")
      .withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", email))
      .first()
    if (!user) throw new ConvexError("Usuário não encontrado no Convex")
    await ctx.db.patch(user._id, { companyId })
    const updated = await ctx.db.get(user._id)
    return updated
  },
})
