import { NextResponse } from "next/server";
import { acessoDoChat, normalizarBusca } from "@/lib/chats/acesso";
import { urlDoAvatar } from "@/lib/chats/servidor";
import { createAdminClient } from "@/lib/supabase-server";

/*
  Lista de conversas do Chats Ktalogu.

  Duas abas, duas perguntas diferentes: "Loja Online" e quem fala com a loja de
  fora (cliente, pedido, orcamento); "Equipe" e quem fala dentro de casa. A
  lista nunca abre as mensagens para se montar - o cabecalho da conversa ja
  guarda quem falou por ultimo e quantas ficaram sem ler.
*/

const LIMITE_PADRAO = 60;
const LIMITE_DA_BUSCA = 250;

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

type LinhaDaConversa = {
  id: string;
  user_id: string;
  kind: string;
  order_id: string | null;
  quote_id: string | null;
  client_id: string | null;
  member_a: string | null;
  member_b: string | null;
  title: string | null;
  visitor_name: string | null;
  visitor_contact: string | null;
  stage: string | null;
  assigned_to: string | null;
  assigned_name: string | null;
  tags: string[] | null;
  follow_up_at: string | null;
  follow_up_note: string | null;
  closed_at: string | null;
  sessions_count: number | null;
  last_message_at: string | null;
  last_message_preview: string | null;
  last_message_author: string | null;
  unread_store: number | null;
  unread_other: number | null;
};

export async function GET(request: Request) {
  const acesso = await acessoDoChat();
  if ("erro" in acesso) return acesso.erro;

  const { searchParams } = new URL(request.url);
  const aba = texto(searchParams.get("aba")) === "equipe" ? "equipe" : "loja";
  const busca = normalizarBusca(texto(searchParams.get("busca")));
  /*
    Tres perguntas, e nao duas.

    "Abertas" e o trabalho do dia e "Encerradas" e o arquivo — mas a pergunta
    que a pessoa faz de verdade quando senta e "quem esta esperando por mim?".
    Nesta a ordem se inverte: quem espera ha mais tempo vem primeiro, porque
    afundar quem espera e o jeito mais rapido de perder um cliente.
  */
  const pedida = texto(searchParams.get("situacao"));
  const situacao = pedida === "encerradas" ? "encerradas" : pedida === "esperando" ? "esperando" : "abertas";
  const limite = busca ? LIMITE_DA_BUSCA : LIMITE_PADRAO;

  const admin = createAdminClient();

  const consulta = admin
    .from("chat_threads")
    .select(
      "id,user_id,kind,order_id,quote_id,client_id,member_a,member_b,title,visitor_name,visitor_contact,stage,assigned_to,assigned_name,tags,follow_up_at,follow_up_note,closed_at,sessions_count,last_message_at,last_message_preview,last_message_author,unread_store,unread_other"
    )
    .order("last_message_at", { ascending: situacao === "esperando", nullsFirst: false })
    .limit(limite);

  if (situacao === "encerradas") {
    consulta.not("closed_at", "is", null);
  } else if (situacao === "esperando") {
    consulta.is("closed_at", null).gt("unread_store", 0);
  } else {
    consulta.is("closed_at", null);
  }

  if (aba === "equipe") {
    consulta.eq("kind", "team").or(`member_a.eq.${acesso.user.id},member_b.eq.${acesso.user.id}`);
  } else {
    if (!acesso.podeVerLoja) {
      return NextResponse.json({ ok: true, conversas: [], meuId: acesso.user.id, lojaIds: [] });
    }
    consulta.in("kind", ["order", "quote", "client", "visitor"]).in("user_id", acesso.tenantIds);
  }

  /*
    Procurar dentro do que foi dito.

    Antes a busca só olhava nome, pedido e a última linha — e "quem foi que
    perguntou de caneca preta?" não tinha resposta, justamente a pergunta que se
    faz. A busca acontece antes da lista porque a conversa certa pode estar
    parada há meses, fora da janela das recentes: sem isto ela nunca apareceria.
  */
  const achadosNasMensagens = new Map<string, string>();
  if (busca && busca.length >= 3) {
    const { data: encontradas } = await admin
      .from("chat_messages")
      .select("thread_id,body,created_at")
      .in("user_id", acesso.tenantIds)
      .ilike("body", `%${busca}%`)
      .is("deleted_at", null)
      .order("created_at", { ascending: false })
      .limit(150);

    ((encontradas || []) as { thread_id: string; body: string | null }[]).forEach((mensagem) => {
      if (!achadosNasMensagens.has(mensagem.thread_id)) {
        achadosNasMensagens.set(mensagem.thread_id, String(mensagem.body || "").slice(0, 120));
      }
    });
  }

  const { data, error } = await consulta;
  if (error) return NextResponse.json({ error: error.message }, { status: 400 });

  const linhas = (data || []) as LinhaDaConversa[];

  /* As conversas antigas que só a busca por mensagem encontrou entram na lista
     pela porta dos fundos, com os mesmos cuidados de dono e situação. */
  const faltando = [...achadosNasMensagens.keys()].filter(
    (id) => !linhas.some((linha) => linha.id === id)
  );
  if (faltando.length) {
    const extra = admin
      .from("chat_threads")
      .select(
        "id,user_id,kind,order_id,quote_id,client_id,member_a,member_b,title,visitor_name,visitor_contact,stage,assigned_to,assigned_name,tags,follow_up_at,follow_up_note,closed_at,sessions_count,last_message_at,last_message_preview,last_message_author,unread_store,unread_other"
      )
      .in("id", faltando.slice(0, 40));

    if (aba === "equipe") {
      extra.eq("kind", "team").or(`member_a.eq.${acesso.user.id},member_b.eq.${acesso.user.id}`);
    } else {
      extra.in("kind", ["order", "quote", "client", "visitor"]).in("user_id", acesso.tenantIds);
    }

    const { data: outras } = await extra;
    linhas.push(...((outras || []) as LinhaDaConversa[]));
  }

  /* Quem e o outro lado de cada conversa: cliente na aba da loja, colega na aba
     da equipe. Duas consultas, e nao uma por linha. */
  const clientIds = Array.from(new Set(linhas.map((linha) => linha.client_id).filter(Boolean))) as string[];
  const orderIds = Array.from(new Set(linhas.map((linha) => linha.order_id).filter(Boolean))) as string[];
  const quoteIds = Array.from(new Set(linhas.map((linha) => linha.quote_id).filter(Boolean))) as string[];
  const membroIds = Array.from(
    new Set(
      linhas
        .flatMap((linha) => [linha.member_a, linha.member_b])
        .filter((id) => Boolean(id) && id !== acesso.user.id)
    )
  ) as string[];

  const [clientes, pedidos, orcamentos, colegas] = await Promise.all([
    clientIds.length
      ? admin.from("clients").select("id,name,company_name,avatar_url").in("id", clientIds)
      : Promise.resolve({ data: [] as never[] }),
    orderIds.length
      ? admin.from("orders").select("id,number,status,art_status").in("id", orderIds)
      : Promise.resolve({ data: [] as never[] }),
    quoteIds.length
      ? admin.from("quotes").select("id,number,status,total").in("id", quoteIds)
      : Promise.resolve({ data: [] as never[] }),
    membroIds.length
      ? admin.from("company_users").select("user_id,email,role").in("user_id", membroIds)
      : Promise.resolve({ data: [] as never[] }),
  ]);

  const porCliente = new Map(
    ((clientes.data || []) as { id: string; name?: string; company_name?: string; avatar_url?: string }[]).map(
      (cliente) => [cliente.id, cliente]
    )
  );
  const porPedido = new Map(
    ((pedidos.data || []) as { id: string; number: number; status?: string; art_status?: string }[]).map((pedido) => [
      pedido.id,
      pedido,
    ])
  );
  const porOrcamento = new Map(
    ((orcamentos.data || []) as { id: string; number: number; status?: string; total?: number }[]).map((orcamento) => [
      orcamento.id,
      orcamento,
    ])
  );
  const porColega = new Map(
    ((colegas.data || []) as { user_id: string; email?: string; role?: string }[]).map((colega) => [
      colega.user_id,
      colega,
    ])
  );

  const conversas = await Promise.all(linhas.map(async (linha) => {
    const souMembroA = linha.member_a === acesso.user.id;
    const outroMembro = linha.kind === "team" ? (souMembroA ? linha.member_b : linha.member_a) : null;
    const colega = outroMembro ? porColega.get(outroMembro) : null;
    const cliente = linha.client_id ? porCliente.get(linha.client_id) : null;
    const pedido = linha.order_id ? porPedido.get(linha.order_id) : null;
    const orcamento = linha.quote_id ? porOrcamento.get(linha.quote_id) : null;

    const nome =
      linha.kind === "team"
        ? texto(colega?.email).split("@")[0] || "Colega"
        : linha.kind === "visitor"
          ? texto(linha.visitor_name) || "Visitante da loja"
          : texto(cliente?.name) || texto(cliente?.company_name) || linha.title || "Conversa";

    const naoLidas =
      linha.kind === "team"
        ? Number((souMembroA ? linha.unread_store : linha.unread_other) || 0)
        : Number(linha.unread_store || 0);

    return {
      id: linha.id,
      kind: linha.kind,
      nome,
      detalhe:
        linha.kind === "team"
          ? texto(colega?.email) || ""
          : pedido
            ? `Pedido #${pedido.number}`
            : orcamento
              ? `Orçamento #${orcamento.number}`
              : linha.kind === "visitor"
                ? texto(linha.visitor_contact) || "Visitante da loja online"
                : linha.title || "",
      avatar: linha.kind === "team" ? null : await urlDoAvatar(admin, cliente?.avatar_url),
      orderId: linha.order_id,
      quoteId: linha.quote_id,
      clientId: linha.client_id,
      artStatus: pedido?.art_status || null,
      ultimaMensagemEm: linha.last_message_at,
      ultimaMensagem: linha.last_message_preview,
      ultimoAutor: linha.last_message_author,
      encerrada: Boolean(linha.closed_at),
      visitas: Number(linha.sessions_count || 1),
      estagio: linha.stage || null,
      responsavel: linha.assigned_to || null,
      responsavelNome: linha.assigned_name || null,
      etiquetas: Array.isArray(linha.tags) ? linha.tags : [],
      retomarEm: linha.follow_up_at,
      retomarNota: linha.follow_up_note,
      naoLidas,
    };
  }));

  const filtradas = busca
    ? conversas
        .filter(
          (conversa) =>
            achadosNasMensagens.has(conversa.id) ||
            [conversa.nome, conversa.detalhe, conversa.ultimaMensagem || ""].some((campo) =>
              normalizarBusca(String(campo)).includes(busca)
            )
        )
        .map((conversa) => ({ ...conversa, trecho: achadosNasMensagens.get(conversa.id) || null }))
    : conversas;

  /* Os dois numeros do trilho: quem esta na aba da equipe precisa ver que a
     loja chamou, e vice-versa. */
  const [pendentesDaLoja, pendentesDaEquipe] = await Promise.all([
    acesso.podeVerLoja
      ? admin
          .from("chat_threads")
          .select("unread_store")
          .in("kind", ["order", "quote", "client", "visitor"])
          .in("user_id", acesso.tenantIds)
          .is("closed_at", null)
          .gt("unread_store", 0)
      : Promise.resolve({ data: [] as { unread_store: number }[] }),
    admin
      .from("chat_threads")
      .select("member_a,unread_store,unread_other")
      .eq("kind", "team")
      .or(`member_a.eq.${acesso.user.id},member_b.eq.${acesso.user.id}`),
  ]);

  const naoLidas = {
    loja: ((pendentesDaLoja.data || []) as { unread_store: number }[]).reduce(
      (soma, linha) => soma + Number(linha.unread_store || 0),
      0
    ),
    equipe: ((pendentesDaEquipe.data || []) as { member_a: string; unread_store: number; unread_other: number }[]).reduce(
      (soma, linha) => soma + Number((linha.member_a === acesso.user.id ? linha.unread_store : linha.unread_other) || 0),
      0
    ),
  };

  /* Quantas conversas — e não quantas mensagens — estão devendo resposta. É o
     número que a aba "Esperando" mostra, porque a pessoa atende conversa, e não
     mensagem solta. */
  const esperando = ((pendentesDaLoja.data || []) as { unread_store: number }[]).length;

  return NextResponse.json({
    ok: true,
    conversas: filtradas.slice(0, LIMITE_PADRAO),
    naoLidas,
    esperando,
    /* A tela precisa saber em que canais ficar ouvindo. */
    meuId: acesso.user.id,
    lojaIds: acesso.tenantIds,
  });
}

/*
  Abre (ou reencontra) a conversa com um colega.

  Duas pessoas nao tem "dono": a dupla e guardada em ordem fixa para que
  A -> B e B -> A caiam sempre no mesmo fio.
*/
export async function POST(request: Request) {
  const acesso = await acessoDoChat();
  if ("erro" in acesso) return acesso.erro;

  const corpo = (await request.json().catch(() => null)) as { membroId?: unknown } | null;
  const membroId = texto(corpo?.membroId);
  if (!membroId || membroId === acesso.user.id) {
    return NextResponse.json({ error: "Escolha alguem da equipe." }, { status: 400 });
  }

  const admin = createAdminClient();

  /* So conversa com quem esta na mesma empresa — e ativo. */
  const { data: colega } = await admin
    .from("company_users")
    .select("user_id,email,company_id,active")
    .eq("user_id", membroId)
    .eq("company_id", acesso.ctx.companyId)
    .maybeSingle();

  if (!colega?.user_id || colega.active === false) {
    return NextResponse.json({ error: "Pessoa nao encontrada na equipe." }, { status: 404 });
  }

  const [a, b] = [acesso.user.id, membroId].sort();

  const { data: existente } = await admin
    .from("chat_threads")
    .select("id")
    .eq("kind", "team")
    .eq("user_id", acesso.ctx.companyId)
    .eq("member_a", a)
    .eq("member_b", b)
    .maybeSingle();

  if (existente?.id) return NextResponse.json({ ok: true, threadId: existente.id });

  const { data: criada, error } = await admin
    .from("chat_threads")
    .insert({
      user_id: acesso.ctx.companyId,
      kind: "team",
      member_a: a,
      member_b: b,
      title: texto(colega.email).split("@")[0] || "Equipe",
    })
    .select("id")
    .single();

  if (error) {
    const { data: depois } = await admin
      .from("chat_threads")
      .select("id")
      .eq("kind", "team")
      .eq("user_id", acesso.ctx.companyId)
      .eq("member_a", a)
      .eq("member_b", b)
      .maybeSingle();
    if (depois?.id) return NextResponse.json({ ok: true, threadId: depois.id });
    return NextResponse.json({ error: error.message }, { status: 400 });
  }

  return NextResponse.json({ ok: true, threadId: criada.id });
}
