import { query, mutation, internalQuery } from "./_generated/server";
import { v } from "convex/values";
import type { Doc } from "./_generated/dataModel";

// Catálogo base de recursos e ações
// Ajuste/expanda conforme os módulos disponíveis no sistema
export const CATALOGO_RECURSOS = [
  {
    recurso: "funcionarios",
    acoes: ["dashboard", "ver", "listar", "criar", "editar", "excluir"],
  },
  {
    recurso: "simbolos",
    acoes: ["dashboard", "ver", "listar", "criar", "editar", "excluir"],
  },
] as const;

export const listarRecursosEAcoes = query({
  args: {},
  returns: v.array(
    v.object({
      recurso: v.string(),
      acoes: v.array(v.string()),
    })
  ),
  handler: async () => {
    return CATALOGO_RECURSOS.map((r) => ({
      recurso: r.recurso,
      acoes: [...r.acoes],
    }));
  },
});

export const listarPermissoesAcoesPorRole = query({
  args: { roleId: v.id("roles") },
  returns: v.array(
    v.object({
      recurso: v.string(),
      acoes: v.array(v.string()),
    })
  ),
  handler: async (ctx, args) => {
    // Buscar vínculos permissao<-role
    const rolePerms = await ctx.db
      .query("rolePermissoes")
      .withIndex("by_role", (q) => q.eq("roleId", args.roleId))
      .collect();

    // Carregar documentos de permissões
    const actionsByResource: Record<string, Set<string>> = {};
    for (const rp of rolePerms) {
      const perm = await ctx.db.get(rp.permissaoId);
      if (!perm) continue;
      const set = (actionsByResource[perm.recurso] ||= new Set<string>());
      set.add(perm.acao);
    }

    // Normalizar para todos os recursos do catálogo
    const result: Array<{ recurso: string; acoes: Array<string> }> = [];
    for (const item of CATALOGO_RECURSOS) {
      const granted = Array.from(
        actionsByResource[item.recurso] ?? new Set<string>()
      );
      result.push({ recurso: item.recurso, acoes: granted });
    }
    return result;
  },
});

export const atualizarPermissaoAcao = mutation({
  args: {
    roleId: v.id("roles"),
    recurso: v.string(),
    acao: v.string(),
    conceder: v.boolean(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    // Garantir documento de permissão (recurso+acao)
    let permissao = await ctx.db
      .query("permissoes")
      .withIndex("by_recurso_e_acao", (q) =>
        q.eq("recurso", args.recurso).eq("acao", args.acao)
      )
      .first();

    if (!permissao) {
      const nome = `${args.recurso}.${args.acao}`;
      const descricao = `Permite ${args.acao} em ${args.recurso}`;
      const id = await ctx.db.insert("permissoes", {
        nome,
        descricao,
        recurso: args.recurso,
        acao: args.acao,
      });
      permissao = await ctx.db.get(id);
    }

    if (!permissao) return null;

    // Verificar vínculo atual
    const existente = await ctx.db
      .query("rolePermissoes")
      .withIndex("by_role", (q) => q.eq("roleId", args.roleId))
      .collect();

    const vinculo = existente.find((rp) => rp.permissaoId === permissao!._id);

    if (args.conceder) {
      if (!vinculo) {
        await ctx.db.insert("rolePermissoes", {
          roleId: args.roleId,
          permissaoId: permissao._id,
        });
      }
    } else {
      if (vinculo) {
        await ctx.db.delete(vinculo._id);
      }
    }
    return null;
  },
});

export const verificarAcao = query({
  args: {
    usuarioId: v.id("usuarios"),
    recurso: v.string(),
    acao: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const usuario = await ctx.db.get(args.usuarioId);
    if (!usuario) throw new Error("acesso_negado");

    const role = await ctx.db.get(usuario.roleId);
    if (!role) throw new Error("acesso_negado");

    // Níveis administrativos têm acesso total
    if (role.nivel <= 1) return null;

    // Encontrar permissão
    const permissao = await ctx.db
      .query("permissoes")
      .withIndex("by_recurso_e_acao", (q) =>
        q.eq("recurso", args.recurso).eq("acao", args.acao)
      )
      .first();
    if (!permissao) throw new Error("acesso_negado");

    const hasLink = await ctx.db
      .query("rolePermissoes")
      .withIndex("by_role", (q) => q.eq("roleId", usuario.roleId))
      .collect();
    const permitido = hasLink.some((rp) => rp.permissaoId === permissao!._id);
    if (!permitido) throw new Error("acesso_negado");
    return null;
  },
});

export const assertPermissaoAcaoAtual = internalQuery({
  args: {
    recurso: v.string(),
    acao: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    let usuarioAtual: Doc<"usuarios"> | null = null;

    if (identity && identity.email) {
      usuarioAtual = await ctx.db
        .query("usuarios")
        .withIndex("by_email", (q) => q.eq("email", identity.email!))
        .first();
    }

    if (!usuarioAtual) {
      const sessaoAtiva = await ctx.db
        .query("sessoes")
        .filter((q) => q.eq(q.field("ativo"), true))
        .order("desc")
        .first();
      if (sessaoAtiva) {
        usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
      }
    }

    if (!usuarioAtual) throw new Error("acesso_negado");

    const role = await ctx.db.get(usuarioAtual.roleId);
    if (!role) throw new Error("acesso_negado");
    if (role.nivel <= 1) return null;

    const permissao = await ctx.db
      .query("permissoes")
      .withIndex("by_recurso_e_acao", (q) =>
        q.eq("recurso", args.recurso).eq("acao", args.acao)
      )
      .first();
    if (!permissao) throw new Error("acesso_negado");

    const links = await ctx.db
      .query("rolePermissoes")
      .withIndex("by_role", (q) => q.eq("roleId", role._id))
      .collect();
    const ok = links.some((rp) => rp.permissaoId === permissao!._id);
    if (!ok) throw new Error("acesso_negado");
    return null;
  },
});
