/**
 * Back office — rede.
 *
 * É a entrada do back office: o que está a acontecer agora e o que precisa de
 * alguém. Substitui o placeholder que estava em /admin.
 *
 * Duas regras que este ecrã segue:
 *
 *   1. **Um alerta que não se resolve não é um alerta, é ruído.** Cada linha
 *      leva a um ecrã com o filtro certo já aplicado
 *   2. **A ordem é por consequência, não por gravidade sentida.** Uma conta que
 *      nunca activou custa a mensalidade inteira; um painel offline custa uma
 *      publicação. Por isso o dinheiro vem primeiro — com uma excepção: uma regra
 *      "não negociável" que não está a ser cumprida (a MFA da equipa) vem antes
 *      de tudo, porque o dano não é uma mensalidade, é um acesso
 *
 * Lê da base de dados desde 21 Set, e desde 23 Set também o custo de IA
 * (token_usage), as fontes com erro e o estado dos painéis na VNNOX. O que não
 * se sabe vem a `null` e mostra-se `—`, em vez de zero. Zero é uma
 * afirmação; `—` é a verdade.
 */

const NOME_INTEGRACAO = {
  anthropic: 'Anthropic', vnnox: 'VNNOX', firecrawl: 'Firecrawl', r2: 'Cloudflare R2',
  meta: 'Meta', moloni: 'Moloni', broadsign: 'Broadsign',
};

// Em linguagem de produto (demo de 12 Out): «simulado — a usar o adaptador falso» e
// «por configurar — sem credenciais» eram perguntas à espera de ser feitas. O estado
// é o mesmo; só a frase mudou. Um erro continua a dizer-se erro.
const ESTADO_INTEGRACAO = {
  ok: { texto: 'a responder', cor: '--online' },
  erro: { texto: 'com erro', cor: '--offline' },
  por_configurar: { texto: 'por activar', cor: '--unknown' },
  simulado: { texto: 'em preparação', cor: '--unknown' },
};

/** O que cada integração soube de útil, numa linha. */
function detalheIntegracao(i) {
  const d = i.detalhe || {};
  if (i.status === 'simulado') return 'tratado à mão por agora';
  if (i.status === 'por_configurar') return 'entra numa fase seguinte';
  if (i.provider === 'firecrawl' && d.creditos !== undefined) return `${d.creditos} de ${d.do_plano} créditos`;
  if (i.provider === 'vnnox' && d.players) return `${d.players} players`;
  if (i.latency_ms !== null && i.latency_ms !== undefined) return `${i.latency_ms} ms`;
  return '';
}

/**
 * A saúde das integrações (D10 do spec, lib/saude.js). Uma célula por
 * fornecedor, na grelha do Efferd. O último incidente fica à vista mesmo com
 * o fornecedor a responder: foi assim que a falta de saldo da Anthropic
 * passava despercebida — a chave responde, o saldo não.
 */
function IntegracoesRede({ saude, notas = [] }) {
  // Recolhido por omissão: é o estado técnico, e não o trabalho do dia (demo de 12 Out).
  // Abre-se sozinho quando há uma falha real — o worker parado ou um fornecedor com erro —,
  // e essas falhas também sobem aos alertas de cima.
  const comFalha = (saude.worker && !saude.worker.vivo) || saude.integracoes.some((i) => i.status === 'erro');
  return (
    <details className="group" open={comFalha}>
      <summary className="mb-3 flex cursor-pointer list-none flex-wrap items-baseline justify-between gap-2">
        <h2 className="label-eyebrow">
          <span aria-hidden="true" className="mr-1 inline-block transition-transform group-open:rotate-90">›</span>
          Integrações
        </h2>
        <span className="text-xs text-muted-foreground">
          {saude.verificado_agora ? 'verificado agora' : 'verificado de 5 em 5 minutos'}
        </span>
      </summary>
      <Painel className="grid grid-cols-2 gap-px bg-[var(--border)] md:grid-cols-4 lg:grid-cols-8">
        {/* O nosso próprio leitor, antes dos fornecedores: sem ele nada do que
            os clientes carregam é lido. Lê-se do sinal de vida, não se verifica. */}
        {saude.worker && (
          <div className="flex flex-col bg-background p-4">
            <span className="text-xs text-muted-foreground">Processamento</span>
            <span className="mt-2 flex items-center gap-1.5 text-sm text-foreground">
              <span
                className="h-2 w-2 shrink-0"
                style={{ background: `hsl(var(${saude.worker.vivo ? '--online' : '--offline'}))` }}
                aria-hidden="true"
              />
              {saude.worker.vivo ? 'a correr' : 'parado'}
            </span>
            <span className="mt-1 text-xs text-muted-foreground">
              {saude.worker.visto_em ? `sinal ${desde(saude.worker.visto_em).texto}` : 'nunca deu sinal'}
            </span>
          </div>
        )}
        {saude.integracoes.map((i) => {
          const e = ESTADO_INTEGRACAO[i.status] || { texto: 'por verificar', cor: '--unknown' };
          return (
            <div key={i.provider} className="flex flex-col bg-background p-4">
              <span className="text-xs text-muted-foreground">{NOME_INTEGRACAO[i.provider] || i.provider}</span>
              <span className="mt-2 flex items-center gap-1.5 text-sm text-foreground">
                <span className="h-2 w-2 shrink-0" style={{ background: `hsl(var(${e.cor}))` }} aria-hidden="true" />
                {e.texto}
              </span>
              <span className="mt-1 text-xs text-muted-foreground">{detalheIntegracao(i)}</span>
              {i.last_incident_at && (
                <span className="mt-2 text-xs text-accent" title={i.last_error || ''}>
                  incidente {desde(i.last_incident_at).texto}
                </span>
              )}
            </div>
          );
        })}
      </Painel>
      {notas.length > 0 && (
        <ul className="mt-3 space-y-1.5">
          {notas.map((linha) => (
            <li key={linha} className="flex gap-2 text-xs text-muted-foreground">
              <span aria-hidden="true">·</span>
              <span>{linha}</span>
            </li>
          ))}
        </ul>
      )}
    </details>
  );
}

// «Configurar no Mac» lia-se como um botão sem sentido numa aplicação web (agentes, 20 Out): é o passo
// a passo para quem tem o computador da instalação (scripts/ensaio-mac), e diz-se assim.
function ConfiguracaoLocalIntegracoes() {
  const [dados,setDados] = React.useState(null);
  const [erro,setErro] = React.useState(null);
  React.useEffect(()=>{let activo=true;api.get('/api/admin/integrations/config').then(r=>{if(activo)setDados(r)}).catch(e=>{if(activo)setErro(e.message||'Não foi possível ler a configuração local.')});return()=>{activo=false}},[]);
  // Fechado por omissão: é conversa da instalação, no ecrã que se partilha numa demo (simplicidade, 3 Nov).
  return <details className="border border-border bg-card p-5 space-y-4">
    <summary className="cursor-pointer"><h2 id="integracoes-config-titulo" className="ns-seccao-titulo inline">Configuração das ligações</h2></summary>
    <div><p className="mt-2 text-sm text-muted-foreground max-w-prose">As chaves ficam neste servidor. Configurado significa que os campos estão preenchidos; não confirma saldo nem ligação ao fornecedor.</p></div>
    {erro&&<p role="status" className="text-sm text-muted-foreground">{erro}</p>}
    {!dados&&!erro&&<p role="status" className="text-sm text-muted-foreground">A ler a configuração…</p>}
    {dados&&<ul className="divide-y divide-border">{dados.servicos.map(s=><li key={s.id} className="py-3 flex flex-wrap items-start justify-between gap-3"><div className="min-w-0"><h3 className="text-sm font-medium">{s.nome}</h3><p className="mt-1 text-xs text-muted-foreground">{s.uso}</p>{s.falta.length>0&&<p className="mt-1 text-xs text-muted-foreground break-words">Falta configurar {s.falta.join(', ')}.</p>}</div><span className="text-xs text-muted-foreground">{{configurado:'Configurado, por verificar',por_configurar:'Por configurar',simulado:'Simulação declarada',adaptador_incorrecto:'Rever adaptador',local:'Armazém local activo',configurado_nao_seleccionado:'Configurado, não seleccionado',sem_adaptador:'Chave guardada, sem gerador integrado',desligado:'Desligado',configurado_desligado:'Configurado, envio desligado'}[s.estado]||'Por verificar'}</span></li>)}</ul>}
    <details className="text-sm"><summary className="cursor-pointer underline underline-offset-4">Onde se põem as chaves</summary><ol className="mt-3 space-y-2 list-decimal pl-5 text-muted-foreground max-w-prose"><li>As chaves não se põem por este ecrã. No Mac onde a aplicação está instalada, na pasta dela, abre <span className="font-mono text-xs">scripts/ensaio-mac/Configurar.command</span>. Introduz as chaves sem as mostrar ou partilhar.</li><li>Abre <span className="font-mono text-xs">Arrancar.command</span> para aplicar a configuração aos serviços.</li><li>Volta à Rede. A configuração local e o estado das integrações são verificações diferentes. Os perfis sociais continuam com publicação manual.</li></ol></details>
  </details>;
}

function ScreenAdminRede({ onNavegar }) {
  const [dados, setDados] = React.useState(null);
  const [erro, setErro] = React.useState(null);

  React.useEffect(() => {
    Promise.all([
      api.get('/api/admin/dashboard/kpis'),
      api.get('/api/admin/accounts'),
      // A saúde das integrações não pode partir o ecrã: sem ela, o resto vale.
      api.get('/api/admin/integrations/health').catch(() => null),
    ])
      .then(([kpis, lista, saude]) => setDados({ kpis, clientes: lista.contas, saude }))
      .catch((e) => setErro(e.message || 'Não foi possível ler o estado da rede.'));
  }, []);

  if (erro) {
    return (
      <div className="space-y-6">
        <header>
          <h1 className="mt-1 font-display text-4xl uppercase leading-none tracking-wide">Rede</h1>
        </header>
        <div className="border border-border bg-card px-6 py-12 text-center">
          <p className="font-display text-xl tracking-wide">Não deu para ler a rede</p>
          <p className="mt-2 text-sm text-muted-foreground">{erro}</p>
        </div>
      </div>
    );
  }

  if (!dados) {
    return (
      <div className="space-y-6">
        <header>
          <h1 className="mt-1 font-display text-4xl uppercase leading-none tracking-wide">Rede</h1>
        </header>
        <SkeletonKpis n={4} />
      </div>
    );
  }

  const { kpis, clientes, saude } = dados;
  const integracao = (p) => (saude ? saude.integracoes.find((i) => i.provider === p) : null);
  const anthropic = integracao('anthropic');
  const firecrawl = integracao('firecrawl');

  // Só depois do prazo do convite (7 dias): um minuto depois de criada, a conta aparecia a vermelho como
  // «nunca foi activada — urgente» (administrador virgem, 4 Nov).
  const SETE_DIAS = 7 * 24 * 3600 * 1000;
  const porActivar = clientes.filter((c) => c.estado === 'por_activar' && (!c.criada_em || Date.now() - new Date(c.criada_em).getTime() > SETE_DIAS));
  const paradas = clientes.filter((c) => {
    if (c.estado !== 'activo') return false;
    const d = desde(c.ultimaActividade);
    return d && d.dias > 30;
  });
  // Uma conta activa onde ninguém entrou ainda não tem última actividade.
  const semNinguemDentro = clientes.filter((c) => c.estado === 'activo' && !c.ultimaActividade);

  const semDono = kpis.paineis.sem_cliente;
  const emManutencao = kpis.paineis.manutencao;
  const receita = kpis.mrr;
  const semPreco = kpis.contas.sob_consulta;

  // O `stats` do dashboard-11 do Efferd, como no dashboard do cliente:
  // ícone, rótulo pequeno, número grande. Ver components/painel.jsx.
  const kpi = (Icone, rotulo, valor, nota, tomNota) => (
    <div className="flex flex-col bg-background p-5">
      <Icone size={18} className="text-primary" />
      <span className="mt-3 text-xs text-muted-foreground">{rotulo}</span>
      <span className="mt-1 font-display text-5xl leading-none tracking-wide tabular-nums">{valor}</span>
      {nota && (
        <span className={`mt-2 text-xs ${tomNota === 'aviso' ? 'text-accent' : 'text-muted-foreground'}`}>
          {nota}
        </span>
      )}
    </div>
  );

  // Ordenadas por consequência. A quantidade entra no texto, não no peso.
  const semMfa = kpis.equipa ? kpis.equipa.sem_mfa : 0;

  const worker = saude && saude.worker;

  const alertas = [
    // Sem o worker nada do que os clientes carregam é lido, nem os factos são
    // extraídos — para todos os clientes. Foi o que aconteceu a 23 Set, e
    // ninguém soube: por isso vem primeiro.
    worker && !worker.vivo && {
      chave: 'worker',
      grave: true,
      titulo: 'O processamento parou',
      detalhe: `${worker.visto_em ? `Sem sinal ${desde(worker.visto_em).texto}.` : 'Nunca deu sinal.'} `
        + 'Os ficheiros das marcas não são lidos e os vídeos não são criados, para nenhum cliente. '
        + 'Avisar a equipa técnica.',
      accao: 'Ver integrações',
      href: '/admin',
    },
    // Um cliente disse que um facto da marca dele está errado: até alguém
    // decidir, esse facto não chega ao Agente PM (lib/factos.js).
    kpis.sinalizacoes_abertas > 0 && {
      chave: 'sinalizacoes',
      grave: false,
      titulo: `${kpis.sinalizacoes_abertas} ${kpis.sinalizacoes_abertas === 1 ? 'sinalização de cliente' : 'sinalizações de clientes'} por responder`,
      detalhe: 'Um cliente disse que um facto da marca está errado. Até decidires, esse facto não chega ao Agente PM.',
      accao: 'Ver clientes',
      href: '/admin/clientes',
    },
    // A Anthropic sem saldo pára o Agente PM de todos os clientes — é o que
    // quase deitou abaixo a demo de 23 Set. Vem antes de tudo.
    anthropic && anthropic.status === 'erro' && {
      chave: 'anthropic',
      grave: true,
      titulo: 'A Anthropic está a recusar chamadas',
      detalhe: `${anthropic.last_error || 'Sem detalhe.'} Enquanto durar, o Agente PM não escreve para nenhum cliente.`,
      accao: 'Ver integrações',
      href: '/admin',
    },
    firecrawl && (firecrawl.status === 'erro' || (firecrawl.detalhe && firecrawl.detalhe.pouco)) && {
      chave: 'firecrawl',
      grave: firecrawl.status === 'erro',
      titulo: firecrawl.status === 'erro' ? 'O Firecrawl não está a ler sites' : 'O Firecrawl está quase sem créditos',
      detalhe: firecrawl.status === 'erro'
        ? (firecrawl.last_error || 'Sem detalhe.')
        : `Restam ${firecrawl.detalhe.creditos} de ${firecrawl.detalhe.do_plano}. Sem eles o crawl pára a meio.`,
      accao: 'Ver integrações',
      href: '/admin',
    },
    // Desde 21 Set o login exige a MFA, por isso isto deixou de ser um furo e
    // passou a ser uma pendência: estas pessoas vão ser obrigadas a configurá-la
    // no próximo acesso. Deixa de ser `grave` por causa disso — um aviso que
    // grita quando já não há perigo ensina a ignorar os que gritam a sério.
    semMfa > 0 && {
      chave: 'sem-mfa',
      grave: false,
      titulo: `${semMfa} ${semMfa === 1 ? 'pessoa da equipa ainda sem MFA' : 'pessoas da equipa ainda sem MFA'}`,
      detalhe:
        'Vão ter de a configurar no próximo acesso — o login não deixa entrar sem isso. Não é preciso fazer nada daqui.',
      accao: 'Ver equipa',
      href: '/admin/equipa',
    },
    porActivar.length && {
      chave: 'por-activar',
      grave: true,
      titulo: `${porActivar.length} ${porActivar.length === 1 ? 'conta nunca foi activada' : 'contas nunca foram activadas'}`,
      // «Ninguém lá entrou» era falso quando só faltavam o contrato e a facturação (teste de raiz, 9 Out):
      // o que falta a cada uma diz-se no «A tratar».
      // Com a concordância pelo número: «1 conta… ainda não estão activas» (agentes, 20 Out).
      detalhe: porActivar.length === 1
        ? `${porActivar[0].nome} — ainda não está activa, e a mensalidade não começa até estar. O «A tratar» diz o que lhe falta.`
        : `${porActivar.map((c) => c.nome).join(', ')} — ainda não estão activas, e a mensalidade não começa até estarem. O «A tratar» diz o que falta a cada uma.`,
      accao: 'Ver em A tratar',
      href: '/admin/fila',
    },
    semDono > 0 && {
      chave: 'sem-dono',
      grave: true,
      titulo: `${semDono} ${semDono === 1 ? 'painel sem cliente atribuído' : 'painéis sem cliente atribuído'}`,
      detalhe:
        'Enquanto não tiverem dono, não há campanha que os alcance — o conteúdo DOOH não tem destino.',
      accao: 'Atribuir',
      href: '/admin/hardware',
    },
    semNinguemDentro.length && {
      chave: 'sem-acesso',
      titulo: `${semNinguemDentro.length} ${semNinguemDentro.length === 1 ? 'conta activa onde ninguém entrou' : 'contas activas onde ninguém entrou'}`,
      // Dizia «a contar mensalidade» também numa conta sob consulta, sem preço no plano (agentes,
      // 20 Out). O que pesa é ninguém ter entrado.
      detalhe: `${semNinguemDentro.map((c) => c.nome).join(', ')} — ${semNinguemDentro.length === 1 ? 'a conta está activa' : 'as contas estão activas'} e não ${semNinguemDentro.length === 1 ? 'teve' : 'tiveram'} um único acesso.`,
      accao: 'Ver clientes',
      href: '/admin/clientes',
    },
    paradas.length && {
      chave: 'paradas',
      titulo: `${paradas.length} ${paradas.length === 1 ? 'conta parada' : 'contas paradas'} há mais de 30 dias`,
      detalhe: paradas
        .map((c) => `${c.nome} (${desde(c.ultimaActividade).texto})`)
        .join(', '),
      accao: 'Ver clientes',
      href: '/admin/clientes',
    },
    // Da VNNOX: um painel atribuído a um cliente e desligado é uma campanha que
    // não passa — e o cliente é o último a saber. Só para os ligados a um player.
    kpis.paineis.offline && kpis.paineis.offline.length > 0 && {
      chave: 'offline',
      grave: true,
      titulo: `${kpis.paineis.offline.length} ${kpis.paineis.offline.length === 1 ? 'painel atribuído está offline' : 'painéis atribuídos estão offline'}`,
      detalhe: `${kpis.paineis.offline.join(', ')} — sem ligação à VNNOX. Confirmar no local se continua a passar.`, // Offline não prova que o ecrã esteja apagado (Codex X-41).
      accao: 'Ver os desligados',
      href: '/admin/hardware?ver=offline',
    },
    kpis.fontes && kpis.fontes.com_erro > 0 && {
      chave: 'fontes-erro',
      titulo: `${kpis.fontes.com_erro} ${kpis.fontes.com_erro === 1 ? 'fonte de conhecimento não deu' : 'fontes de conhecimento não deram'} para ler`,
      detalhe: `Em ${kpis.fontes.contas} ${kpis.fontes.contas === 1 ? 'conta' : 'contas'}. O agente dessas marcas sabe menos do que devia — algumas resolvem-se do lado do cliente, um site que nos barrou, por exemplo.`,
      accao: 'Ver clientes',
      href: '/admin/clientes',
    },
    emManutencao > 0 && {
      chave: 'manutencao',
      titulo: `${emManutencao} ${emManutencao === 1 ? 'painel em manutenção' : 'painéis em manutenção'}`,
      detalhe: 'Fora de serviço e fora de stock — não contam como disponíveis para atribuir.',
      accao: 'Ver painéis',
      href: '/admin/hardware',
    },
  ].filter(Boolean);

  // O que o estado dos painéis não cobre, dito junto das integrações (demo de 12 Out:
  // era uma caixa à parte, «O que este ecrã ainda não vigia», no ecrã de entrada).
  const semLeitura = kpis.paineis.total - (kpis.paineis.ligados_vnnox || 0);
  const notasPaineis = [
    kpis.paineis.offline === null && kpis.paineis.ligados_vnnox > 0
      ? 'O estado dos painéis não respondeu agora; o alerta de painel desligado volta quando responder.'
      : semLeitura > 0
      ? `${semLeitura} ${semLeitura === 1 ? 'painel ainda não envia' : 'painéis ainda não enviam'} o estado à distância: se se desligarem, não aparece alerta.`
      : null,
  ].filter(Boolean);

  return (
    <div className="space-y-9">
      <header>
        <h1 className="mt-1 font-display text-4xl uppercase leading-none tracking-wide">Rede</h1>
        <p className="mt-2 text-sm text-muted-foreground">
          {kpis.contas.total} contas · {kpis.paineis.total} painéis · {kpis.lojas} lojas
        </p>
      </header>

      <section>
        <Painel className="grid grid-cols-1 gap-px bg-[var(--border)] sm:grid-cols-2 lg:grid-cols-4">
          {kpi(
            IconLojas,
            'Contas activas',
            kpis.contas.activas,
            kpis.contas.por_activar ? `${kpis.contas.por_activar} por activar` : 'todas activas',
            kpis.contas.por_activar ? 'aviso' : null
          )}
          {kpi(
            IconPaineis,
            'Painéis atribuídos',
            `${kpis.paineis.total - semDono - emManutencao}/${kpis.paineis.total}`,
            // «7/8 — todos atribuídos» contradizia-se: o que falta é o da manutenção.
            semDono
              ? `${semDono} por atribuir${emManutencao ? ` · ${emManutencao} em manutenção` : ''}`
              : emManutencao
              ? `${emManutencao} em manutenção, os outros atribuídos`
              : 'todos atribuídos',
            semDono ? 'aviso' : null
          )}
          {kpis.receita_visivel === false
            ? kpi(IconResultados, 'Receita mensal', '—', 'só o Admin vê a receita')
            : kpi(IconResultados, 'Receita mensal', euros(receita), 'das contas activas com preço de tabela')}
          {kpis.custo_ia_mes === null
            ? kpi(IconAgente, 'Custo de IA do mês', '—', 'só o Admin vê o custo')
            : kpi(IconAgente, 'Custo de IA do mês', euros(kpis.custo_ia_mes), 'o que os modelos de IA custaram este mês')}
        </Painel>
        {semPreco > 0 && kpis.receita_visivel !== false && (
          <p className="mt-4 text-xs text-muted-foreground">
            {semPreco} {semPreco === 1 ? 'conta está' : 'contas estão'} em plano sob consulta e não
            {semPreco === 1 ? ' entra' : ' entram'} na conta da receita.
          </p>
        )}
      </section>

      <Painel>
        <PainelMeta className="pb-0">
          <h2><PainelTitulo Icone={IconAlerta}>Precisa de alguém</PainelTitulo></h2>
          <span className="font-mono text-xs tabular-nums text-muted-foreground">{alertas.length}</span>
        </PainelMeta>
        {alertas.length === 0 ? (
          <PainelConteudo>
            <p className="text-sm text-foreground">Nada por resolver.</p>
            <p className="mt-1 text-xs text-muted-foreground">
              Todas as contas activas, todos os painéis atribuídos e a ler.
            </p>
          </PainelConteudo>
        ) : (
          <ul className="relative z-[1] mt-3">
            {alertas.map((a) => (
              <li key={a.chave} className="border-t border-border">
                <div className="flex flex-wrap items-start gap-4 px-4 py-3 lg:px-5">
                  <span
                    className="mt-1 h-2 w-2 shrink-0"
                    style={{ background: `hsl(var(${a.grave ? '--offline' : '--accent'}))` }}
                    aria-hidden="true"
                  />
                  <div className="min-w-0 flex-1">
                    <p className="text-sm text-foreground">
                      {a.titulo}
                      <span className="sr-only">{a.grave ? ' — urgente' : ' — a rever'}</span>
                    </p>
                    <p className="mt-1 text-xs text-muted-foreground">{a.detalhe}</p>
                  </div>
                  <a
                    href={a.href}
                    onClick={(e) => {
                      e.preventDefault();
                      onNavegar(a.href);
                    }}
                    className="shrink-0 border border-border px-3 py-1.5 text-xs uppercase tracking-wider text-muted-foreground outline-none hover:border-primary hover:text-primary focus-visible:ring-1 focus-visible:ring-accent"
                  >
                    {a.accao}
                  </a>
                </div>
              </li>
            ))}
          </ul>
        )}
      </Painel>

      {saude && <IntegracoesRede saude={saude} notas={notasPaineis} />}
      <ConfiguracaoLocalIntegracoes />
    </div>
  );
}
