import type { SupabaseClient, User } from "@supabase/supabase-js";
import { ACAO_POR_CHAVE, CATALOGO, ORDEM_NIVEL, cadeiaDe, type NivelModulo } from "./catalogo-permissoes.ts";

export type CompanyMembership = {
  companyId: string;
  role: CompanyRole;
  permissions: unknown;
  active: boolean;
};

export type CompanyRole =
  | "owner"
  | "admin"
  | "manager"
  | "financial"
  | "production"
  | "sales"
  | "viewer";

export type CompanyPermission =
  | "users.manage"
  | "subscription.manage"
  | "company.settings"
  | "dashboard.read"
  | "pdv.read"
  | "products.read"
  | "products.write"
  | "categories.read"
  | "categories.write"
  | "catalog.read"
  | "catalog.write"
  | "clients.read"
  | "clients.write"
  | "quotes.read"
  | "quotes.write"
  | "orders.read"
  | "orders.write"
  | "orders.edit"
  | "chats.read"
  | "chats.write"
  | "chats.settings"
  | "financial.read"
  | "financial.write"
  /* Uma chave por TELA do Financeiro. Todas herdam de financial.read ou
     financial.write, entao quem tem o modulo hoje continua com as onze
     telas; o ajuste fino so passa a valer quando o admin marcar. */
  | "financial.overview.read"
  | "financial.payables.read"
  | "financial.payables.write"
  | "financial.payables.settle"
  | "financial.fixedcosts.read"
  | "financial.fixedcosts.write"
  | "financial.receivables.read"
  | "financial.receivables.write"
  | "financial.receivables.settle"
  | "financial.payments.read"
  | "financial.cashflow.read"
  | "financial.cashflow.write"
  | "financial.bank.read"
  | "financial.bank.write"
  | "financial.dre.read"
  | "financial.links.read"
  | "financial.links.write"
  | "financial.amounts.see"
  | "commissions.read"
  | "commissions.write"
  | "commissions.adjust"
  | "commissions.close"
  | "commissions.approve"
  | "commissions.pay"
  | "commissions.self"
  | "commissions.profile"
  | "ia.read"
  | "ia.write"
  | "agenda.read"
  | "agenda.write"
  | "pricing.read"
  | "pricing.write"
  /* Modulos que respondiam por chave emprestada e agora tem a propria. Herdam a
     antiga enquanto ninguem configurar — ver `herdaDe` no catalogo. */
  | "suppliers.read"
  | "suppliers.write"
  | "production.read"
  | "production.write"
  | "materials.read"
  | "materials.write"
  | "reports.read"
  | "whatsapp.manage"
  | "marketing.read"
  | "marketing.manage"
  /* Uma chave por TELA dos demais modulos. Mesma regra do Financeiro:
     todas herdam da chave antiga do modulo, entao ninguem perde acesso. */
  | "catalog.store.read"
  | "catalog.store.write"
  | "catalog.dashboard.read"
  | "catalog.products.read"
  | "catalog.products.write"
  | "catalog.orders.read"
  | "catalog.orders.write"
  | "catalog.banners.read"
  | "catalog.banners.write"
  | "catalog.stories.read"
  | "catalog.stories.write"
  | "catalog.reviews.read"
  | "catalog.reviews.write"
  | "pricing.config.read"
  | "pricing.config.write"
  | "pricing.wizard.read"
  | "pricing.health.read"
  | "pricing.taxes.read"
  | "pricing.taxes.write"
  | "products.catalog.read"
  | "products.catalog.write"
  | "products.stock.read"
  | "products.stock.write"
  | "suppliers.list.read"
  | "suppliers.list.write"
  | "suppliers.xbz.read"
  | "production.board.read"
  | "production.board.write"
  | "production.costs.read"
  | "production.costs.write"
  | "clients.list.read"
  | "clients.list.write"
  | "clients.ranking.read"
  | "agenda.board.read"
  | "agenda.board.write"
  | "agenda.report.read"
  | "company.data.read"
  | "company.data.write"
  | "company.shipping.read"
  | "company.shipping.write"
  /* Criar chaves de acesso e ligar assistentes (Claude, ChatGPT, Codex) a conta. */
  | "company.assistants.manage"
  /* As quatro que faltavam para toda acao ter tela. */
  | "pricing.health.write"
  | "suppliers.xbz.write"
  | "production.board.columns"
  | "financial.bank.integrations"
  /* Notas fiscais (Spedy): ver, emitir, cancelar/corrigir e configurar. */
  | "fiscal.ver"
  | "fiscal.emitir"
  | "fiscal.cancelar"
  | "fiscal.configurar"
  | "fiscal.recebidas"
  /* Produtos digitais: a area propria (visao geral, produtos e cursos, alunos,
     comentarios, entregas, arquivos e configuracoes). */
  | "digital.read"
  | "digital.write"
  | "digital.overview.read"
  | "digital.products.read"
  | "digital.products.write"
  | "digital.students.read"
  | "digital.students.write"
  | "digital.comments.read"
  | "digital.comments.write"
  | "digital.deliveries.read"
  | "digital.deliveries.write"
  | "digital.files.read"
  | "digital.files.write"
  | "digital.settings.read"
  | "digital.settings.write";

export const companyPermissions: CompanyPermission[] = [
  "agenda.read",
  "agenda.write",
  "users.manage",
  "subscription.manage",
  "company.settings",
  "dashboard.read",
  "pdv.read",
  "products.read",
  "products.write",
  "categories.read",
  "categories.write",
  "catalog.read",
  "catalog.write",
  "clients.read",
  "clients.write",
  "quotes.read",
  "quotes.write",
  "orders.read",
  "orders.write",
  "orders.edit",
  "chats.read",
  "chats.write",
  "chats.settings",
  "financial.read",
  "financial.write",
  "financial.overview.read",
  "financial.payables.read",
  "financial.payables.write",
  "financial.payables.settle",
  "financial.fixedcosts.read",
  "financial.fixedcosts.write",
  "financial.receivables.read",
  "financial.receivables.write",
  "financial.receivables.settle",
  "financial.payments.read",
  "financial.cashflow.read",
  "financial.cashflow.write",
  "financial.bank.read",
  "financial.bank.write",
  "financial.dre.read",
  "financial.links.read",
  "financial.links.write",
  "financial.amounts.see",
  "commissions.read",
  "commissions.write",
  "commissions.adjust",
  "commissions.close",
  "commissions.approve",
  "commissions.pay",
  "commissions.self",
  "commissions.profile",
  "ia.read",
  "ia.write",
  "pricing.read",
  "pricing.write",
  "suppliers.read",
  "suppliers.write",
  "production.read",
  "production.write",
  "materials.read",
  "materials.write",
  "reports.read",
  "whatsapp.manage",
  "marketing.read",
  "marketing.manage",
  "catalog.store.read",
  "catalog.store.write",
  "catalog.dashboard.read",
  "catalog.products.read",
  "catalog.products.write",
  "catalog.orders.read",
  "catalog.orders.write",
  "catalog.banners.read",
  "catalog.banners.write",
  "catalog.stories.read",
  "catalog.stories.write",
  "catalog.reviews.read",
  "catalog.reviews.write",
  "pricing.config.read",
  "pricing.config.write",
  "pricing.wizard.read",
  "pricing.health.read",
  "pricing.taxes.read",
  "pricing.taxes.write",
  "products.catalog.read",
  "products.catalog.write",
  "products.stock.read",
  "products.stock.write",
  "suppliers.list.read",
  "suppliers.list.write",
  "suppliers.xbz.read",
  "production.board.read",
  "production.board.write",
  "production.costs.read",
  "production.costs.write",
  "clients.list.read",
  "clients.list.write",
  "clients.ranking.read",
  "agenda.board.read",
  "agenda.board.write",
  "agenda.report.read",
  "company.data.read",
  "company.data.write",
  "company.shipping.read",
  "company.shipping.write",
  "company.assistants.manage",
  "pricing.health.write",
  "suppliers.xbz.write",
  "production.board.columns",
  "financial.bank.integrations",
  "fiscal.ver",
  "fiscal.emitir",
  "fiscal.cancelar",
  "fiscal.configurar",
  "fiscal.recebidas",
  "digital.read",
  "digital.write",
  "digital.overview.read",
  "digital.products.read",
  "digital.products.write",
  "digital.students.read",
  "digital.students.write",
  "digital.comments.read",
  "digital.comments.write",
  "digital.deliveries.read",
  "digital.deliveries.write",
  "digital.files.read",
  "digital.files.write",
  "digital.settings.read",
  "digital.settings.write",
];

export type CompanyContext = {
  companyId: string;
  ownerId: string | null;
  membership: CompanyMembership | null;
  tenantIds: string[];
};

let cached:
  | {
      userId: string;
      value: CompanyContext;
      fetchedAt: number;
    }
  | null = null;

export async function getCompanyContext(
  supabase: SupabaseClient,
  user: User
): Promise<CompanyContext> {
  if (cached?.userId === user.id && Date.now() - cached.fetchedAt < 15000) return cached.value;

  const { data, error } = await supabase
    .from("company_users")
    .select("company_id,role,permissions,active")
    .eq("user_id", user.id)
    .maybeSingle();

  const tenantIdsSet = new Set<string>();
  tenantIdsSet.add(user.id);

  if (error || !data?.company_id) {
    let resolvedCompanyId = user.id;

    const companyByOwner = await supabase
      .from("companies")
      .select("id")
      .eq("owner_id", user.id)
      .maybeSingle();

    if (!companyByOwner.error && (companyByOwner.data as { id?: unknown } | null)?.id) {
      const id = (companyByOwner.data as { id?: unknown }).id;
      if (typeof id === "string" && id) {
        resolvedCompanyId = id;
        tenantIdsSet.add(id);
      }
    }

    const value: CompanyContext = {
      companyId: resolvedCompanyId,
      ownerId: user.id,
      membership: null,
      tenantIds: Array.from(tenantIdsSet),
    };
    cached = { userId: user.id, value, fetchedAt: Date.now() };
    return value;
  }

  const companyId = data.company_id as string;
  tenantIdsSet.add(companyId);

  let resolvedOwnerId: string | null = null;
  const companyOwner = await supabase
    .from("companies")
    .select("owner_id")
    .eq("id", data.company_id)
    .maybeSingle();

  if (!companyOwner.error && (companyOwner.data as { owner_id?: unknown } | null)?.owner_id) {
    const ownerIdValue = (companyOwner.data as { owner_id?: unknown }).owner_id;
    if (typeof ownerIdValue === "string" && ownerIdValue) {
      resolvedOwnerId = ownerIdValue;
    }
  }

  if (!resolvedOwnerId) {
    const ownershipProbe = await supabase
      .from("companies")
      .select("id")
      .eq("id", companyId)
      .eq("owner_id", user.id)
      .maybeSingle();
    if (!ownershipProbe.error && (ownershipProbe.data as { id?: unknown } | null)?.id) {
      resolvedOwnerId = user.id;
    }
  }

  if (resolvedOwnerId) tenantIdsSet.add(resolvedOwnerId);

  const value: CompanyContext = {
    companyId,
    ownerId: resolvedOwnerId,
    membership: {
      companyId: data.company_id,
      role: data.role as CompanyRole,
      permissions: data.permissions,
      active: data.active === false ? false : true,
    },
    tenantIds: Array.from(tenantIdsSet),
  };

  cached = { userId: user.id, value, fetchedAt: Date.now() };
  return value;
}

export function getEffectiveCompanyRole(ctx: CompanyContext, userId: string): CompanyRole {
  if (ctx.ownerId && ctx.ownerId === userId) return "owner";
  if (ctx.membership?.role) return ctx.membership.role;
  return "viewer";
}

export function shouldUseEmployeeUi(ctx: CompanyContext, userId: string): boolean {
  const role = getEffectiveCompanyRole(ctx, userId);
  return role !== "owner" && role !== "admin";
}

export function resolveCompanyPublicProfileId(ctx: CompanyContext, userId: string): string {
  return ctx.ownerId || userId;
}

export function canManageCompanyPublicIdentity(ctx: CompanyContext, userId: string): boolean {
  const publicProfileId = resolveCompanyPublicProfileId(ctx, userId);
  if (publicProfileId === userId) return true;
  return !shouldUseEmployeeUi(ctx, userId);
}

export function canEditCompanyPublicIdentityFromCurrentProfile(ctx: CompanyContext, userId: string): boolean {
  return resolveCompanyPublicProfileId(ctx, userId) === userId;
}

function normalizePermissionOverrides(raw: unknown): Record<string, boolean> {
  if (!raw || typeof raw !== "object") return {};
  if (Array.isArray(raw)) return {};
  const record = raw as Record<string, unknown>;
  const normalized: Record<string, boolean> = {};
  for (const [key, value] of Object.entries(record)) {
    if (typeof value === "boolean") normalized[key] = value;
  }
  return normalized;
}

/* Nivel escolhido para um modulo inteiro, quando o admin usa so o basico.
   Guardado no mesmo objeto das acoes, com a chave sendo o nome do modulo:
   `{"orders": "operar"}` ao lado de `{"orders.edit": true}`. */
function lerNivelDoModulo(permissions: unknown, modulo: string): NivelModulo | null {
  if (!permissions || typeof permissions !== "object" || Array.isArray(permissions)) return null;
  const valor = (permissions as Record<string, unknown>)[modulo];
  if (typeof valor !== "string") return null;
  const normalizado = valor.toLowerCase();
  return normalizado === "nenhum" ||
    normalizado === "ver" ||
    normalizado === "operar" ||
    normalizado === "total"
    ? normalizado
    : null;
}

/**
 * Resolve uma permissão. A ordem das camadas é a regra:
 *
 *   1. Ajuste da ação — vence tudo, inclusive dono e admin
 *   2. Dono ou admin
 *   3. Nível escolhido para o módulo
 *   4. Padrão do papel, vindo do catálogo
 *   5. A chave herdada, repetindo 1, 3 e 4
 *   6. Negado
 *
 * A camada 1 vir antes da 2 não é detalhe: há empresas que tiraram algo de um
 * admin por ali, e inverter mudaria acesso em produção sem ninguém pedir.
 *
 * Pela mesma razão, os ajustes são procurados na cadeia inteira antes de
 * dono/admin responder: `financial.read: false` continua barrando Fornecedores
 * como barra hoje, e não passa a liberar por causa da chave nova.
 *
 * Espelha `checar_permissao_empresa` no banco. Quem protege o dado é o banco;
 * isto existe para o navegador decidir sem ida ao servidor.
 */
export function canCompanyRole(role: CompanyRole, permissions: unknown, permission: CompanyPermission): boolean {
  const overrides = normalizePermissionOverrides(permissions);
  const cadeia = cadeiaDe(permission);

  // 1. Ajuste explícito, do mais específico para o herdado.
  for (const chave of cadeia) {
    if (Object.prototype.hasOwnProperty.call(overrides, chave)) return overrides[chave] === true;
  }

  // 2. Dono e admin, sem ajuste em contrário em nenhum ponto da cadeia.
  if (role === "owner" || role === "admin") return true;

  // 3 e 4, repetidos ao longo da cadeia até alguém decidir.
  for (const chave of cadeia) {
    const acao = ACAO_POR_CHAVE.get(chave);
    /* Fora do catálogo é negado, como sempre foi: é o que impede um erro de
       digitação em código novo de virar permissão aberta. */
    if (!acao) return false;

    const nivel = lerNivelDoModulo(permissions, acao.modulo);
    if (nivel && acao.nivelMinimo) {
      /* Nível reconhecido decide sozinho — escolher "ver" precisa negar o que é
         de operar, senão o padrão do papel reabriria na camada seguinte e a
         escolha do admin não valeria nada. */
      return ORDEM_NIVEL[nivel] >= ORDEM_NIVEL[acao.nivelMinimo];
    }

    if (acao.papeisPadrao.includes(role)) return true;
  }

  return false;
}

export function canCompany(ctx: CompanyContext, userId: string, permission: CompanyPermission): boolean {
  const role = getEffectiveCompanyRole(ctx, userId);
  return canCompanyRole(role, ctx.membership?.permissions, permission);
}

/** Basta uma da lista. */
export function canAnyCompany(
  ctx: CompanyContext,
  userId: string,
  permissions: CompanyPermission[]
): boolean {
  return permissions.some((permission) => canCompany(ctx, userId, permission));
}

/**
 * CONSULTA DE APOIO NÃO É TELA.
 *
 * A lista de bancos, a de categorias e a de fornecedores não são páginas: são
 * o vocabulário que várias telas do Financeiro precisam para escrever uma
 * linha. O Fluxo de Caixa mostra em que banco cada lançamento caiu; Contas a
 * pagar mostra a categoria e o fornecedor de cada conta.
 *
 * Quando essas consultas passaram a exigir a chave da tela DELAS, quem tinha
 * permissão para o Fluxo de Caixa abria a tela e ela vinha vazia — a página
 * carregava, a consulta de apoio levava 403, e a lista nunca aparecia. Era
 * uma permissão negando outra em silêncio.
 *
 * A regra: quem pode ler QUALQUER tela do Financeiro pode consultar o apoio
 * dele. O que continua trancado é a tela própria de cada assunto (Contas
 * bancárias tem a sua) e qualquer escrita.
 */
/**
 * CONSULTA DE APOIO NAO E TELA, EM QUALQUER MODULO.
 *
 * A lista de bancos, a de fornecedores, a de categorias e a configuracao de
 * precificacao nao sao paginas: sao o apoio que varias paginas usam para
 * escrever uma linha. Exigir delas a chave de UMA tela esvazia todas as
 * vizinhas — foi assim que o fluxo de caixa chegou vazio para a funcionaria.
 *
 * Entao a regra e: quem pode ler QUALQUER tela do modulo pode fazer a consulta
 * de apoio dele. A lista sai do catalogo, e nao de um `const` escrito a mao,
 * porque a chave nova de amanha precisa entrar aqui sozinha.
 */
export function leiturasDoModulo(modulo: string): CompanyPermission[] {
  return CATALOGO.filter((m) => m.chave === modulo)
    .flatMap((m) => m.acoes)
    .filter((a) => a.chave.endsWith(".read"))
    .map((a) => a.chave);
}

/** Pode fazer a consulta de apoio deste modulo? */
export function canReadModuleLookup(
  ctx: CompanyContext,
  userId: string,
  modulo: string
): boolean {
  return canAnyCompany(ctx, userId, leiturasDoModulo(modulo));
}

/* Escrita a mao esta lista ficava para tras a cada tela nova do Financeiro —
   e ficar para tras aqui e a tela vizinha chegar vazia. Agora sai do catalogo. */
export const LEITURAS_DO_FINANCEIRO: CompanyPermission[] = leiturasDoModulo("financial");

/** Pode consultar o apoio do financeiro (bancos, categorias, fornecedores). */
export function canReadFinancialLookup(ctx: CompanyContext, userId: string): boolean {
  return canAnyCompany(ctx, userId, LEITURAS_DO_FINANCEIRO);
}

/** Pode consultar o apoio de fornecedores (a lista, para escrever uma linha). */
export function canReadSupplierLookup(ctx: CompanyContext, userId: string): boolean {
  return canReadModuleLookup(ctx, userId, "suppliers");
}

export function canManageFinancialCritical(ctx: CompanyContext, userId: string): boolean {
  const role = getEffectiveCompanyRole(ctx, userId);
  return role === "owner" || role === "admin";
}

/**
 * Resolve o nome do usuário baseado no ID do criador (created_by)
 * Retorna o formato apropriado para exibição na interface
 */
export async function resolveCreatedByDisplayName(
  supabase: SupabaseClient,
  createdById: string,
  currentUserId: string,
  companyContext: CompanyContext
): Promise<string> {
  if (!createdById) return "Sistema";
  
  // Se for o próprio usuário atual
  if (createdById === currentUserId) {
    const role = getEffectiveCompanyRole(companyContext, currentUserId);
    if (role === "owner") {
      return "Empresa (Admin)";
    }
    
    // Buscar nome do funcionário
    const { data: employeeProfile } = await supabase
      .from("employee_profiles")
      .select("full_name, role")
      .eq("user_id", currentUserId)
      .maybeSingle();
      
    if (employeeProfile?.full_name) {
      return `${employeeProfile.full_name}${employeeProfile.role ? ` (${employeeProfile.role})` : ''}`;
    }
    
    return "Usuário";
  }
  
  // Para outros usuários, verificar se são donos ou funcionários
  const targetRole = getEffectiveCompanyRole(companyContext, createdById);
  
  if (targetRole === "owner") {
    return "Empresa (Admin)";
  }
  
  // Buscar perfil do funcionário
  const { data: employeeProfile } = await supabase
    .from("employee_profiles")
    .select("full_name, role")
    .eq("user_id", createdById)
    .maybeSingle();
    
  if (employeeProfile?.full_name) {
    return `${employeeProfile.full_name}${employeeProfile.role ? ` (${employeeProfile.role})` : ''}`;
  }
  
  // Fallback: buscar email do usuário
  const { data: user } = await supabase.auth.admin.getUserById(createdById);
  if (user?.user?.email) {
    return user.user.email;
  }
  
  return "Usuário Desconhecido";
}
