import type { SupabaseClient } from "@supabase/supabase-js";

import type { CompanyPermission } from "@/lib/company-context";

import {
  colunasDaObservacao,
  comMensagem,
  descreverValor,
  lerMensagem,
  lerObservacao,
  mesmoValor,
  MENSAGENS,
  QUAIS_MENSAGENS,
  tituloDe,
  type Observacao,
  type QualMensagem,
  type TipoDePersonalizacao,
} from "./catalogo";

/**
 * As personalizações no banco: ler e gravar cada uma, registrar o que mudou e
 * desfazer.
 *
 * Ler e gravar a configuração em si usam o Supabase que chega de fora: o da
 * pessoa (pelo assistente ou pelo painel), com as travas do banco valendo. O
 * registro do que mudou usa a chave de serviço, como o resto dos assistentes:
 * a tabela é fechada para o navegador.
 */

/** A permissão que cada personalização pede: a mesma da tela em que ela mora. */
export const PERMISSAO_DO_TIPO: Record<TipoDePersonalizacao, CompanyPermission> = {
  mensagem_do_whatsapp: "whatsapp.manage",
  observacao_do_orcamento: "quotes.write",
};

export const TIPOS: TipoDePersonalizacao[] = ["mensagem_do_whatsapp", "observacao_do_orcamento"];

export function ehTipo(valor: unknown): valor is TipoDePersonalizacao {
  return (TIPOS as unknown[]).includes(valor);
}

export type PerfilPersonalizavel = {
  whatsapp_templates: unknown;
  quote_default_notes: unknown;
  quote_default_notes_enabled: unknown;
};

export async function lerPerfil(supabase: SupabaseClient, donoId: string): Promise<PerfilPersonalizavel> {
  const { data, error } = await supabase
    .from("profiles")
    .select("whatsapp_templates,quote_default_notes,quote_default_notes_enabled")
    .eq("id", donoId)
    .maybeSingle();
  if (error) throw new Error(error.message);
  return (data ?? { whatsapp_templates: null, quote_default_notes: null, quote_default_notes_enabled: false }) as PerfilPersonalizavel;
}

function qualDaChave(alvo: string | null): QualMensagem | null {
  return QUAIS_MENSAGENS.find((qual) => MENSAGENS[qual].chave === alvo) ?? null;
}

/** O valor de hoje: o texto da mensagem (`null` = padrão) ou a observação. */
export function valorAtual(perfil: PerfilPersonalizavel, tipo: TipoDePersonalizacao, alvo: string | null): unknown {
  if (tipo === "mensagem_do_whatsapp") {
    const qual = qualDaChave(alvo);
    return qual ? lerMensagem(perfil.whatsapp_templates, qual) : null;
  }
  return lerObservacao(perfil);
}

/**
 * Grava um valor. Devolve "travado" quando o banco não deixou mudar nada: as
 * configurações moram no perfil do dono, e quem não é o dono precisa também
 * da permissão de configurações da empresa.
 */
export async function gravarValor(
  supabase: SupabaseClient,
  donoId: string,
  tipo: TipoDePersonalizacao,
  alvo: string | null,
  valor: unknown
): Promise<"ok" | "travado"> {
  if (tipo === "observacao_do_orcamento") {
    const { data, error } = await supabase.from("profiles").update(colunasDaObservacao(valor as Observacao)).eq("id", donoId).select("id");
    if (error) throw new Error(error.message);
    return (data ?? []).length ? "ok" : "travado";
  }

  const qual = qualDaChave(alvo);
  if (!qual) throw new Error(`mensagem desconhecida: ${alvo}`);
  /* As mensagens moram todas no mesmo campo. Para não apagar a que o painel
     gravou no mesmo instante, a gravação só vale se o campo ainda for o que
     foi lido; se mudou no meio, lê de novo e tenta outra vez. */
  for (let tentativa = 0; tentativa < 3; tentativa += 1) {
    const lido = (await lerPerfil(supabase, donoId)).whatsapp_templates ?? null;
    const novo = comMensagem(lido, qual, typeof valor === "string" ? valor : null);
    const consulta = supabase.from("profiles").update({ whatsapp_templates: novo }).eq("id", donoId);
    const { data, error } = await (lido === null ? consulta.is("whatsapp_templates", null) : consulta.eq("whatsapp_templates", JSON.stringify(lido))).select("id");
    if (error) throw new Error(error.message);
    if ((data ?? []).length) return "ok";
    /* Nada mudou. Se o campo é o mesmo que foi lido, foi o banco que não
       deixou; se é outro, alguém gravou no meio. */
    const agora = (await lerPerfil(supabase, donoId)).whatsapp_templates ?? null;
    if (mesmoValor(agora, lido)) return "travado";
  }
  throw new Error("as mensagens do WhatsApp mudaram várias vezes enquanto gravava");
}

export const MENSAGEM_DO_TRAVADO =
  "O banco não deixou gravar: as configurações moram no perfil do dono da empresa, e para mudá-las a pessoa precisa também da permissão “Configurações da empresa”. Quem libera é o dono, no painel, em Funcionários.";

export async function registrarPersonalizacao(
  admin: SupabaseClient,
  dados: {
    companyId: string;
    donoId: string;
    chaveId: string | null;
    usuarioId: string;
    tipo: TipoDePersonalizacao;
    alvo: string | null;
    antes: unknown;
    depois: unknown;
  }
): Promise<number | null> {
  const { data, error } = await admin
    .from("personalizacoes_dos_assistentes")
    .insert({
      company_id: dados.companyId,
      dono_id: dados.donoId,
      chave_id: dados.chaveId,
      usuario_id: dados.usuarioId,
      tipo: dados.tipo,
      alvo: dados.alvo,
      titulo: tituloDe(dados.tipo, dados.alvo),
      antes: dados.antes ?? null,
      depois: dados.depois ?? null,
    })
    .select("id")
    .single();
  if (error || !data) {
    console.error("[assistentes] não foi possível registrar a personalização:", error?.message);
    return null;
  }
  return (data as { id: number }).id;
}

export type PersonalizacaoNaLista = {
  id: number;
  tipo: TipoDePersonalizacao;
  titulo: string;
  antes: string;
  depois: string;
  quando: string;
  por: string | null;
  de: string | null;
  minha: boolean;
  desfeitaEm: string | null;
  /* A pessoa que está olhando tem a permissão desta configuração. */
  podeDesfazer: boolean;
};

type LinhaDaPersonalizacao = {
  id: number;
  company_id: string;
  dono_id: string;
  usuario_id: string | null;
  tipo: string;
  alvo: string | null;
  titulo: string;
  antes: unknown;
  depois: unknown;
  criada_em: string;
  desfeita_em: string | null;
  chaves_de_acesso?: { nome?: string | null; usuario_email?: string | null } | Array<{ nome?: string | null; usuario_email?: string | null }> | null;
};

const COLUNAS = "id,company_id,dono_id,usuario_id,tipo,alvo,titulo,antes,depois,criada_em,desfeita_em,chaves_de_acesso(nome,usuario_email)";

/**
 * As personalizações da empresa, as mais novas primeiro. Dono e admin veem as
 * de todo mundo; os demais, só as que vieram dos assistentes deles.
 */
export async function listarPersonalizacoes(
  admin: SupabaseClient,
  quem: { companyId: string; usuarioId: string; verTodas: boolean; pode: (permissao: CompanyPermission) => boolean },
  limite = 30
): Promise<PersonalizacaoNaLista[]> {
  let consulta = admin
    .from("personalizacoes_dos_assistentes")
    .select(COLUNAS)
    .eq("company_id", quem.companyId)
    .order("criada_em", { ascending: false })
    .limit(limite);
  if (!quem.verTodas) consulta = consulta.eq("usuario_id", quem.usuarioId);
  const { data, error } = await consulta;
  if (error) throw new Error(error.message);

  return ((data ?? []) as unknown as LinhaDaPersonalizacao[])
    .filter((linha) => ehTipo(linha.tipo))
    .map((linha) => {
      const tipo = linha.tipo as TipoDePersonalizacao;
      const chave = Array.isArray(linha.chaves_de_acesso) ? linha.chaves_de_acesso[0] : linha.chaves_de_acesso;
      return {
        id: linha.id,
        tipo,
        titulo: linha.titulo,
        antes: descreverValor(tipo, linha.antes),
        depois: descreverValor(tipo, linha.depois),
        quando: linha.criada_em,
        por: chave?.nome ?? null,
        de: chave?.usuario_email ?? null,
        minha: linha.usuario_id === quem.usuarioId,
        desfeitaEm: linha.desfeita_em,
        podeDesfazer: !linha.desfeita_em && quem.pode(PERMISSAO_DO_TIPO[tipo]),
      };
    });
}

export type PreparoDoDesfazer =
  | { ok: true; linha: LinhaDaPersonalizacao & { tipo: TipoDePersonalizacao }; atual: unknown }
  | { ok: false; mensagem: string };

/**
 * Confere se dá para desfazer: a personalização é desta empresa (e desta
 * pessoa, para quem não vê as de todos), ainda não foi desfeita, a pessoa tem
 * a permissão da configuração, e ninguém mexeu nela depois. Desfazer por cima
 * de uma mudança mais nova apagaria essa mudança sem ninguém ver.
 */
export async function prepararDesfazer(
  admin: SupabaseClient,
  supabase: SupabaseClient,
  pedido: { id: number; companyId: string; usuarioId: string; verTodas: boolean; pode: (permissao: CompanyPermission) => boolean }
): Promise<PreparoDoDesfazer> {
  const { data, error } = await admin
    .from("personalizacoes_dos_assistentes")
    .select(COLUNAS)
    .eq("id", pedido.id)
    .eq("company_id", pedido.companyId)
    .maybeSingle();
  if (error) throw new Error(error.message);
  const linha = (data ?? null) as unknown as LinhaDaPersonalizacao | null;
  if (!linha || !ehTipo(linha.tipo) || (!pedido.verTodas && linha.usuario_id !== pedido.usuarioId)) {
    return { ok: false, mensagem: "Personalização não encontrada." };
  }
  if (linha.desfeita_em) return { ok: false, mensagem: "Esta personalização já foi desfeita." };
  if (!pedido.pode(PERMISSAO_DO_TIPO[linha.tipo])) {
    return { ok: false, mensagem: "Esta pessoa não tem permissão para mexer nesta configuração. Quem libera é o dono da empresa, no painel, em Funcionários." };
  }

  const atual = valorAtual(await lerPerfil(supabase, linha.dono_id), linha.tipo, linha.alvo);
  if (!mesmoValor(atual, linha.depois)) {
    return {
      ok: false,
      mensagem: `${linha.titulo} mudou depois desta personalização. Para não apagar a mudança mais nova, ela não é desfeita por aqui: ajuste pelo painel, ou desfaça primeiro a mais recente.`,
    };
  }
  return { ok: true, linha: linha as LinhaDaPersonalizacao & { tipo: TipoDePersonalizacao }, atual };
}

/** Volta a configuração ao valor de antes e marca a personalização como desfeita. */
export async function aplicarDesfazer(
  admin: SupabaseClient,
  supabase: SupabaseClient,
  preparo: Extract<PreparoDoDesfazer, { ok: true }>,
  quem: { usuarioId: string }
): Promise<{ ok: true } | { ok: false; mensagem: string }> {
  const { linha } = preparo;
  /* Marca primeiro, só se ninguém marcou: dois cliques juntos não desfazem duas vezes. */
  const { data: marcada, error: erroAoMarcar } = await admin
    .from("personalizacoes_dos_assistentes")
    .update({ desfeita_em: new Date().toISOString(), desfeita_por: quem.usuarioId })
    .eq("id", linha.id)
    .is("desfeita_em", null)
    .select("id");
  if (erroAoMarcar) throw new Error(erroAoMarcar.message);
  if (!(marcada ?? []).length) return { ok: false, mensagem: "Esta personalização já foi desfeita." };

  /* Não deu para voltar o valor: a marca sai, para a pessoa poder tentar de novo. */
  const desmarcar = () => admin.from("personalizacoes_dos_assistentes").update({ desfeita_em: null, desfeita_por: null }).eq("id", linha.id);
  let gravado: "ok" | "travado";
  try {
    gravado = await gravarValor(supabase, linha.dono_id, linha.tipo, linha.alvo, linha.antes);
  } catch (erro) {
    await desmarcar();
    throw erro;
  }
  if (gravado === "travado") {
    await desmarcar();
    return { ok: false, mensagem: MENSAGEM_DO_TRAVADO };
  }
  return { ok: true };
}
