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

/*
  Chats Ktalogu — as regras da conversa em um lugar so.

  Antes cada assunto tinha a sua propria caixa de mensagens e a mesma logica
  (marcar como lida, resumir o fio, contar nao lidas) ia sendo reescrita em cada
  rota. Aqui a conversa e uma so: o pedido, o orcamento, o cliente e a equipe
  entram como vinculo da mesma linha.
*/

type Admin = SupabaseClient;

export const BUCKET_DA_CONVERSA = "quote-arts";
/* Anexo novo mora aqui, numa pasta PRIVADA (migration 161): o arquivo que o
   cliente manda na conversa nao pode ficar num endereco publico. So sai por
   /api/chats/anexo/<mensagem>, que confere quem pede e assina na hora.
   `quote-arts` continua so para os anexos antigos. */
export const BUCKET_PRIVADO_DA_CONVERSA = "conversas";
export const TAMANHO_MAXIMO_DO_ANEXO = 8 * 1024 * 1024;

export const CAMPOS_DA_MENSAGEM =
  "id,thread_id,author_type,author_id,author_name,body,attachment_url,attachment_path,attachment_name,attachment_kind,card,reply_to,edited_at,internal,is_art,art_status,art_decided_at,art_decision_note,created_at,deleted_at,deleted_by,read_by_store_at,read_by_other_at";

export type MensagemDoBanco = {
  id: string;
  thread_id: string;
  author_type: "store" | "client" | "team" | "system";
  author_id?: string | null;
  author_name?: string | null;
  body?: string | null;
  attachment_url?: string | null;
  attachment_path?: string | null;
  attachment_name?: string | null;
  attachment_kind?: string | null;
  card?: Record<string, unknown> | null;
  reply_to?: string | null;
  edited_at?: string | null;
  internal?: boolean | null;
  is_art?: boolean | null;
  art_status?: string | null;
  art_decided_at?: string | null;
  art_decision_note?: string | null;
  created_at: string;
  deleted_at?: string | null;
  deleted_by?: string | null;
  read_by_store_at?: string | null;
  read_by_other_at?: string | null;
};

/*
  A tela do pedido e a do portal ja falam "sender" e "read_by_client_at". Trocar
  o nome dos campos no banco nao precisa virar trabalho de tela: a traducao mora
  aqui.
*/
export function mensagemParaTela(mensagem: MensagemDoBanco, ladoDeQuemLe?: "store" | "other") {
  const apagada = Boolean(mensagem.deleted_at);
  const lidaPeloOutro = ladoDeQuemLe === "other" ? mensagem.read_by_store_at : mensagem.read_by_other_at;
  return {
    id: mensagem.id,
    sender: mensagem.author_type === "client" ? "client" : "store",
    author_type: mensagem.author_type,
    author_id: mensagem.author_id || null,
    lida_pelo_outro: lidaPeloOutro || null,
    sender_name: mensagem.author_name || null,
    body: apagada ? null : mensagem.body || null,
    attachment_url: apagada ? null : enderecoDoAnexo(mensagem),
    attachment_name: apagada ? null : mensagem.attachment_name || null,
    attachment_kind: apagada ? null : mensagem.attachment_kind || null,
    card: apagada ? null : mensagem.card || null,
    reply_to: mensagem.reply_to || null,
    edited_at: mensagem.edited_at || null,
    internal: Boolean(mensagem.internal),
    is_art: apagada ? false : Boolean(mensagem.is_art),
    art_status: apagada ? null : mensagem.art_status || null,
    art_decided_at: mensagem.art_decided_at || null,
    art_decision_note: apagada ? null : mensagem.art_decision_note || null,
    created_at: mensagem.created_at,
    deleted_at: mensagem.deleted_at || null,
    read_by_store_at: mensagem.read_by_store_at || null,
    read_by_client_at: mensagem.read_by_other_at || null,
  };
}

export function resumoDaMensagem(mensagem: {
  body?: string | null;
  attachment_kind?: string | null;
  card?: Record<string, unknown> | null;
  deleted_at?: string | null;
}) {
  if (mensagem.deleted_at) return "Mensagem apagada";
  const corpo = String(mensagem.body || "").trim();
  if (corpo) return corpo.slice(0, 120);
  if (mensagem.attachment_kind === "image") return "Imagem";
  if (mensagem.attachment_kind) return "Arquivo";
  /* Cartao sem texto ainda precisa dizer o que e na lista de conversas, senao a
     linha fica vazia e parece mensagem perdida. */
  if (mensagem.card) {
    const tipo = String((mensagem.card as { tipo?: string }).tipo || "");
    const titulo = String((mensagem.card as { titulo?: string }).titulo || "").trim();
    if (tipo === "produto") return titulo ? `Produto: ${titulo}` : "Produto";
    if (tipo === "cupom") return titulo ? `Cupom ${titulo}` : "Cupom";
  }
  return "";
}

/*
  Encontra (ou abre) a conversa do pedido.

  O pedido pode existir ha meses sem nunca ter tido conversa; a linha so nasce
  quando alguem precisa dela — abrir a aba ja basta, porque e ali que a loja
  espera ver o fio.
*/
export async function conversaDoPedido(
  admin: Admin,
  pedido: { id: string; user_id: string; number?: number | null; client_id?: string | null }
) {
  const { data: existente } = await admin
    .from("chat_threads")
    .select("id,user_id,unread_store,unread_other")
    .eq("kind", "order")
    .eq("order_id", pedido.id)
    .maybeSingle();

  if (existente?.id) return existente as { id: string; user_id: string };

  const { data: criada, error } = await admin
    .from("chat_threads")
    .insert({
      user_id: pedido.user_id,
      kind: "order",
      order_id: pedido.id,
      client_id: pedido.client_id || null,
      title: pedido.number ? `Pedido #${pedido.number}` : "Pedido",
    })
    .select("id,user_id")
    .single();

  /* Corrida entre duas abas abertas ao mesmo tempo: a segunda le a que venceu. */
  if (error) {
    const { data: depois } = await admin
      .from("chat_threads")
      .select("id,user_id")
      .eq("kind", "order")
      .eq("order_id", pedido.id)
      .maybeSingle();
    if (depois?.id) return depois as { id: string; user_id: string };
    throw new Error(error.message);
  }

  return criada as { id: string; user_id: string };
}

export async function listarMensagensDaConversa(
  admin: Admin,
  threadId: string,
  limite = 200,
  /* Ponto de corte para buscar o que veio antes: e o "carregar mais" de quem
     precisa achar o que foi combinado la atras. */
  antes?: string
) {
  let consulta = admin
    .from("chat_messages")
    .select(CAMPOS_DA_MENSAGEM)
    .eq("thread_id", threadId)
    .order("created_at", { ascending: false })
    .limit(limite);

  if (antes) consulta = consulta.lt("created_at", antes);

  const { data, error } = await consulta;

  if (error) throw new Error(error.message);

  /* Vem do fim para o comeco para nao carregar anos de conversa; a tela mostra
     na ordem em que foi falada. */
  return ((data || []) as MensagemDoBanco[]).slice().reverse();
}

/*
  Quem esta lendo zera o proprio contador; o do outro lado continua de pe.

  Na conversa da equipe nao existe loja e cliente: as duas pessoas escrevem do
  mesmo jeito. Ali o que separa "meu" de "do outro" e quem assinou a mensagem,
  e nao o tipo de autor.
*/
export async function marcarConversaComoLida(
  admin: Admin,
  threadId: string,
  lado: "store" | "other",
  ehEquipe = false,
  meuId?: string
) {
  const agora = new Date().toISOString();
  const campo = lado === "store" ? "read_by_store_at" : "read_by_other_at";

  const consulta = admin
    .from("chat_messages")
    .update({ [campo]: agora })
    .eq("thread_id", threadId)
    .is(campo, null);

  if (ehEquipe) {
    if (meuId) consulta.neq("author_id", meuId);
  } else {
    consulta.in("author_type", lado === "store" ? ["client"] : ["store", "team", "system"]);
  }

  await consulta;

  await admin
    .from("chat_threads")
    .update(lado === "store" ? { unread_store: 0 } : { unread_other: 0 })
    .eq("id", threadId);
}

/*
  Guarda no cabecalho da conversa o que a lista precisa mostrar.

  Sem isto, montar a lista de conversas exigiria abrir todas as mensagens de
  todos os fios so para saber quem falou por ultimo.
*/
export async function atualizarResumoDaConversa(
  admin: Admin,
  threadId: string,
  mensagem: {
    body?: string | null;
    attachment_kind?: string | null;
    card?: Record<string, unknown> | null;
    author_type: string;
    created_at?: string;
  },
  paraQuemNaoLeu: "store" | "other" | null
) {
  const { data: atual } = await admin
    .from("chat_threads")
    .select("unread_store,unread_other")
    .eq("id", threadId)
    .maybeSingle();

  const naoLidasDaLoja = Number(atual?.unread_store || 0);
  const naoLidasDoOutro = Number(atual?.unread_other || 0);

  await admin
    .from("chat_threads")
    .update({
      last_message_at: mensagem.created_at || new Date().toISOString(),
      last_message_preview: resumoDaMensagem(mensagem) || null,
      last_message_author: mensagem.author_type,
      updated_at: new Date().toISOString(),
      unread_store: paraQuemNaoLeu === "store" ? naoLidasDaLoja + 1 : naoLidasDaLoja,
      unread_other: paraQuemNaoLeu === "other" ? naoLidasDoOutro + 1 : naoLidasDoOutro,
    })
    .eq("id", threadId);
}

/* O anexo mora no armazenamento; apagar a mensagem sem apagar o arquivo deixaria
   o endereco publico funcionando para quem tivesse guardado o link. */
export function caminhoDoAnexo(url?: string | null) {
  return String(url || "").split(`/${BUCKET_DA_CONVERSA}/`)[1]?.split("?")[0] || "";
}

/* O endereco que a tela usa para abrir o anexo. O novo passa pela rota que
   confere quem pede; o antigo segue com o endereco publico que ja tinha. */
export function enderecoDoAnexo(mensagem: { id: string; attachment_path?: string | null; attachment_url?: string | null }) {
  if (mensagem.attachment_path) return `/api/chats/anexo/${mensagem.id}`;
  return mensagem.attachment_url || null;
}

/* Apaga o arquivo da mensagem, esteja na pasta privada (novo) ou na publica
   (antigo). */
export async function apagarArquivoDaMensagem(
  admin: Admin,
  mensagem: { attachment_path?: string | null; attachment_url?: string | null }
) {
  if (mensagem.attachment_path) {
    await admin.storage.from(BUCKET_PRIVADO_DA_CONVERSA).remove([mensagem.attachment_path]);
    return;
  }
  const caminho = caminhoDoAnexo(mensagem.attachment_url);
  if (caminho) await admin.storage.from(BUCKET_DA_CONVERSA).remove([caminho]);
}

/*
  Avatar do cliente.

  No banco o que fica guardado e o caminho dentro do armazenamento, e nao um
  endereco que o navegador consiga abrir. Sem assinar, a foto da lista viraria
  um quadrado quebrado.
*/
export async function urlDoAvatar(admin: Admin, valor?: string | null) {
  const bruto = String(valor || "").trim();
  if (!bruto) return null;
  if (bruto.startsWith("data:")) return bruto;

  let caminho = bruto.replace(/^\/+/, "");
  if (/^https?:\/\//i.test(bruto)) {
    const marcador = "/storage/v1/object/public/";
    const assinado = "/storage/v1/object/sign/";
    const qual = bruto.includes(assinado) ? assinado : marcador;
    if (!bruto.includes(qual)) return bruto;
    caminho = bruto.slice(bruto.indexOf(qual) + qual.length);
  }

  caminho = caminho
    .replace(/^storage\/v1\/object\/public\//, "")
    .replace(/^storage\/v1\/object\/sign\//, "")
    .split("?")[0];

  const [balde, ...partes] = caminho.split("/");
  if (!balde || !partes.length) return bruto;

  const { data: assinada } = await admin.storage.from(balde).createSignedUrl(partes.join("/"), 60 * 60 * 24 * 30);
  if (assinada?.signedUrl) return assinada.signedUrl;

  const { data: publica } = admin.storage.from(balde).getPublicUrl(partes.join("/"));
  return publica?.publicUrl || bruto;
}
