#!/usr/bin/env python3
"""
Cenario de teste MULTI-TENANT do Contextia (Etapa 9) -- um ERP ficticio de
gestao de clinicas, o **ERP Vitalis**, com DOIS clientes finais.

O QUE ESTE SCRIPT E
-------------------
Um gerador de DADOS DE TESTE, irmao de `cenario-demo.py`. A diferenca esta no
ALCANCE: la e um cliente so; aqui sao dois clientes finais do MESMO ERP, cada
um no seu projeto, com a MESMA estrutura de dados e conteudo diferente.

    tenant  erp-vitalis
      +-- projeto clinica-aurora     -> chave do cliente A
      +-- projeto clinica-bemestar   -> chave do cliente B
      (+ uma chave de escopo TENANT: o admin geral do ERP)

Cada projeto recebe tres datasets identicos em ESTRUTURA:

    pacientes       ~25 registros   cadastro dos pacientes da clinica
    procedimentos   ~10 registros   catalogo de procedimentos (mesmos codigos
                                    nas duas, com preco de tabela proprio)
    faturamento     ~60 registros   guias faturadas contra convenio/particular

E a estrutura igual com dados diferentes que faz o cenario provar as duas
coisas que interessam na Etapa 9:

  * ISOLAMENTO -- a chave da Aurora nunca alcanca a view da Bem-Estar, mesmo
    que o nome do dataset seja o mesmo nas duas.
  * CONSOLIDADO -- a chave de tenant alcanca as duas de uma vez, e por isso
    consegue responder "compare o faturamento em aberto das duas clinicas"
    num unico SELECT com UNION ALL.

NENHUM DADO E REAL. Nomes, CPFs, convenios e valores sao inventados por um
gerador deterministico.

IDEMPOTENCIA
------------
Igual ao cenario-demo: todo envelope vai com `key: "ID"` (o servidor faz
UPSERT) e semente fixa por clinica, entao reexecutar ATUALIZA os mesmos
registros em vez de duplicar. As DATAS sao ancoradas em `date.today()` de
proposito -- "o que vence este mes" tem que ter resposta viva em qualquer dia
que o cenario for reexecutado.

CICLO DE CARGA
--------------
Cada dataset e enviado como uma carga identificada (`sync` igual em todos os
lotes, `complete: true` so no ultimo). Se algum lote falhar, o `complete` nao
e enviado e nada e removido -- a regra de ouro de `contextia_client.py`. Cada
clinica ganha o SEU proprio `sync`: sao cargas independentes, de projetos
independentes, e misturar os identificadores so confundiria a auditoria.

STATUS_ID E OPACO DE PROPOSITO (e agora a explicacao mora no TENANT)
--------------------------------------------------------------------
As guias tem STATUS_ID 1, 2, 3 ou 9 e a descricao do campo enviada em `fields`
diz apenas "codigo da situacao da guia no ERP". O significado NAO viaja nos
dados. No cenario de um cliente so (`cenario-demo.py`) ele estava num documento
do projeto; aqui ele esta no NIVEL DO TENANT -- definido uma vez em
`erp-vitalis` e herdado pelas duas clinicas. E o ponto da etapa: num ERP com
mil clientes, `STATUS_ID = 3` nao pode ser cadastrado mil vezes.

A Aurora sobrescreve `status_id.3` com o significado dela (auditoria interna
antes do recurso), e a Bem-Estar continua com a regra da casa -- e assim o
cenario prova tambem a SOBRESCRITA.

AS CHAVES NAO MORAM AQUI
------------------------
Este arquivo nao guarda chave de verdade -- so o placeholder `ctx_...`. As
chaves reais saem de `tools/setup-multitenant.sh` e estao documentadas em
`docs/CENARIO-MULTITENANT.md`. Passe-as por parametro ou por variavel de
ambiente:

    export CONTEXTIA_KEY_AURORA=ctx_...
    export CONTEXTIA_KEY_BEMESTAR=ctx_...
    python3 tools/cenario-multitenant.py

    python3 tools/cenario-multitenant.py \
        --key-aurora=ctx_... --key-bemestar=ctx_...

    python3 tools/cenario-multitenant.py --clinica=aurora --key-aurora=ctx_...
    python3 tools/cenario-multitenant.py --dry-run      # so imprime envelopes
    python3 tools/cenario-multitenant.py --limite=5     # demonstra a remocao

O envio propriamente dito e reaproveitado de `contextia_client.py` (mesma
pasta): este script so monta os registros e escolhe o destino pela chave.
"""

import argparse
import datetime
import json
import os
import random
import sys

# contextia_client mudou de lugar: mora em sdk/coleta/, junto do conector.
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'sdk', 'coleta'))

from contextia_client import (
    L_CHUNK,
    L_INTAKE_URL,
    L_TIMEOUT,
    enviar_dataset,
    fatiar,
    gerar_sync,
    montar_envelope,
)


# ---------------------------------------------------------------------------
# Placeholder. NUNCA coloque uma chave real aqui: o destino vem de --key-* ou
# das variaveis de ambiente CONTEXTIA_KEY_AURORA / CONTEXTIA_KEY_BEMESTAR.
# ---------------------------------------------------------------------------
L_KEY_PLACEHOLDER = "ctx_troque_pela_chave_do_projeto"

L_QTD_PACIENTES = 25
L_QTD_FATURAMENTO = 60


# ---------------------------------------------------------------------------
# Vocabulario clinico ficticio. Acentuacao real: o cenario tambem serve para
# provar que UTF-8 atravessa API, banco, view SQL e resposta do MCP intacto.
# ---------------------------------------------------------------------------
L_NOMES = [
    "Ana", "Beatriz", "Carlos", "Daniela", "Eduardo", "Fernanda", "Gustavo",
    "Helena", "Ícaro", "Juliana", "Kléber", "Larissa", "Marcelo", "Natália",
    "Otávio", "Patrícia", "Rafael", "Simone", "Tadeu", "Úrsula", "Vinícius",
    "Wanessa", "Yasmin", "Zilda", "Bruno", "Camila", "Diego", "Elisa",
    "Fábio", "Gabriela", "Henrique", "Isabela", "Joaquim", "Karina",
    "Leonardo", "Mariana", "Nilton", "Olívia", "Paulo", "Renata",
]

L_SOBRENOMES = [
    "Almeida", "Barbosa", "Carvalho", "Duarte", "Esteves", "Fonseca",
    "Guimarães", "Henriques", "Iglesias", "Juvenal", "Kowalski", "Lacerda",
    "Machado", "Nogueira", "Oliveira", "Pacheco", "Queiroz", "Rezende",
    "Siqueira", "Teixeira", "Ustra", "Valadares", "Werneck", "Xavier",
    "Zambrano", "Antunes", "Bittencourt", "Cordeiro", "Drummond", "Fagundes",
]

L_ESPECIALIDADES = [
    "Clínica Geral", "Cardiologia", "Dermatologia", "Ortopedia",
    "Ginecologia", "Oftalmologia", "Endocrinologia", "Fisioterapia",
]


# ---------------------------------------------------------------------------
# Catalogo de procedimentos: os MESMOS codigos nas duas clinicas.
#
# Isso e deliberado. Se cada clinica tivesse um catalogo proprio, comparar as
# duas exigiria um de-para -- e o cenario deixaria de exercitar o consolidado.
# Com o codigo compartilhado, a chave de tenant consegue perguntar "qual clinica
# fatura mais em Cardiologia?" com um GROUP BY simples. O que muda de uma para
# a outra e o VALORTABELA (cada clinica negocia a sua tabela) e a duracao.
# ---------------------------------------------------------------------------
L_PROCEDIMENTOS = [
    (1, "Consulta ambulatorial", "Clínica Geral", 180.00, 30),
    (2, "Consulta cardiológica", "Cardiologia", 260.00, 40),
    (3, "Eletrocardiograma", "Cardiologia", 145.00, 20),
    (4, "Consulta dermatológica", "Dermatologia", 240.00, 30),
    (5, "Biópsia de pele", "Dermatologia", 620.00, 45),
    (6, "Consulta ortopédica", "Ortopedia", 250.00, 30),
    (7, "Infiltração articular", "Ortopedia", 880.00, 50),
    (8, "Ultrassonografia obstétrica", "Ginecologia", 310.00, 35),
    (9, "Mapeamento de retina", "Oftalmologia", 295.00, 25),
    (10, "Sessão de fisioterapia motora", "Fisioterapia", 120.00, 50),
]


# ---------------------------------------------------------------------------
# Perfil de cada cliente final. E o unico lugar onde as duas clinicas diferem:
# semente, convenios aceitos, cidades, faixa de IDs e o fator de tabela.
#
# As FAIXAS DE ID nao se sobrepoem (Aurora 1xx/3xxx, Bem-Estar 2xx/6xxx) --
# nao por necessidade tecnica (os projetos ja sao bancos logicos separados),
# mas para que um numero visto numa resposta consolidada do admin diga sozinho
# de qual clinica ele veio.
# ---------------------------------------------------------------------------
L_CLINICAS = {
    "aurora": {
        "projeto": "clinica-aurora",
        "nome": "Clínica Aurora",
        "seed": 20260829,
        "env": "CONTEXTIA_KEY_AURORA",
        "base_paciente": 100,
        "base_guia": 3000,
        "fator_tabela": 1.00,
        "convenios": ["Saúde Total", "Unimed Regional", "Particular", "Vida Plena"],
        "cidades": [("Curitiba", "PR"), ("São José dos Pinhais", "PR"),
                    ("Colombo", "PR"), ("Pinhais", "PR"), ("Araucária", "PR")],
    },
    "bemestar": {
        "projeto": "clinica-bemestar",
        "nome": "Clínica Bem-Estar",
        "seed": 20260830,
        "env": "CONTEXTIA_KEY_BEMESTAR",
        "base_paciente": 200,
        "base_guia": 6000,
        "fator_tabela": 1.18,
        "convenios": ["Saúde Total", "Amil Nacional", "Particular", "Bradesco Saúde"],
        "cidades": [("Florianópolis", "SC"), ("São José", "SC"),
                    ("Palhoça", "SC"), ("Biguaçu", "SC"), ("Itajaí", "SC")],
    },
}


# ---------------------------------------------------------------------------
# Datasets: descricao do dataset e descricao de CADA CAMPO.
#
# As descricoes falam do CAMPO (o que a coluna guarda, em que formato, o que
# referencia) -- nunca do significado dos VALORES. O servidor transforma cada
# uma numa definicao semantica `<dataset>.<campo>` DO PROJETO, com source
# `client`.
#
# Repare em STATUS_ID: descrito como "codigo da situacao", sem tabela de
# valores. O que cada codigo significa esta definido no NIVEL DO TENANT
# (`status_id.1`, `status_id.2`, ...) e herdado pelas duas clinicas -- ver o
# docstring do modulo e `docs/CENARIO-MULTITENANT.md`.
#
# A `description` do dataset e o unico texto que nomeia a clinica: e por ela
# que o admin, olhando a resposta consolidada, sabe de quem e cada view.
# ---------------------------------------------------------------------------
def montar_datasets(p_clinica):
    """Metadados dos tres datasets, com o nome da clinica na descricao."""
    loc_nome = p_clinica["nome"]

    return {
        "pacientes": {
            "description": "Cadastro de pacientes da %s, extraído do ERP Vitalis." % loc_nome,
            "key": "ID",
            "fields": {
                "ID": "Código interno do paciente no ERP; é o valor referenciado por PACIENTE_ID em faturamento.",
                "NOME": "Nome completo do paciente conforme o cadastro.",
                "CPF": "CPF do paciente, gravado com a pontuação (000.000.000-00).",
                "DATANASCIMENTO": "Data de nascimento do paciente, no formato AAAA-MM-DD.",
                "CONVENIO": "Nome do convênio ao qual o paciente está vinculado no cadastro.",
                "CIDADE": "Município do endereço residencial do paciente.",
                "ESTADO": "Sigla da unidade federativa do endereço residencial (duas letras).",
                "DATACADASTRO": "Data em que o paciente foi cadastrado na clínica, no formato AAAA-MM-DD.",
            },
        },
        "procedimentos": {
            "description": "Catálogo de procedimentos executados pela %s, com o preço de tabela negociado." % loc_nome,
            "key": "ID",
            "fields": {
                "ID": "Código interno do procedimento; é o valor referenciado por PROCEDIMENTO_ID em faturamento.",
                "DESCRICAO": "Nome do procedimento como aparece na guia e nos relatórios.",
                "ESPECIALIDADE": "Especialidade médica responsável pela execução do procedimento.",
                "VALORTABELA": "Preço de tabela do procedimento nesta clínica, em reais.",
                "DURACAOMINUTOS": "Duração média da execução do procedimento, em minutos.",
            },
        },
        "faturamento": {
            "description": "Guias faturadas pela %s contra convênios e pacientes particulares." % loc_nome,
            "key": "ID",
            "fields": {
                "ID": "Código interno da guia de faturamento no ERP.",
                "PACIENTE_ID": "Código do paciente atendido; referencia o campo ID do dataset pacientes.",
                "PROCEDIMENTO_ID": "Código do procedimento executado; referencia o campo ID do dataset procedimentos.",
                "GUIA": "Número da guia emitida para o atendimento.",
                "CONVENIO": "Nome do convênio faturado nesta guia, ou Particular quando o paciente pagou direto.",
                "DATAATENDIMENTO": "Data em que o atendimento foi realizado, no formato AAAA-MM-DD.",
                "DATAVENCIMENTO": "Data prevista para o repasse do convênio ou para o pagamento particular, no formato AAAA-MM-DD.",
                "VALOR": "Valor faturado da guia, em reais.",
                "SALDO": "Parcela do valor que ainda não foi recebida, em reais.",
                "STATUS_ID": "Código numérico da situação da guia no ERP.",
            },
        },
    }


# ------------------------------------------  Utilitarios de geracao
def gerar_cpf(p_rng):
    """CPF ficticio, so com a forma visual certa (nao valida digito)."""
    return "%03d.%03d.%03d-%02d" % (
        p_rng.randint(100, 999), p_rng.randint(100, 999),
        p_rng.randint(100, 999), p_rng.randint(10, 99),
    )


def dinheiro(p_rng, p_min, p_max):
    """Valor em reais com centavos variados (nunca redondo demais)."""
    return round(p_rng.uniform(p_min, p_max), 2)


def dia(p_hoje, p_offset):
    """Data ISO deslocada em dias a partir de hoje."""
    return (p_hoje + datetime.timedelta(days=p_offset)).isoformat()


# ------------------------------------------  pacientes
def gerar_pacientes(p_rng, p_hoje, p_clinica):
    loc_registros = []
    loc_usados = set()

    for loc_i in range(L_QTD_PACIENTES):
        while True:
            loc_nome = "%s %s %s" % (
                p_rng.choice(L_NOMES), p_rng.choice(L_SOBRENOMES),
                p_rng.choice(L_SOBRENOMES),
            )
            if loc_nome not in loc_usados:
                loc_usados.add(loc_nome)
                break

        loc_cidade, loc_uf = p_rng.choice(p_clinica["cidades"])

        loc_registros.append({
            "ID": p_clinica["base_paciente"] + loc_i + 1,
            "NOME": loc_nome,
            "CPF": gerar_cpf(p_rng),
            # Entre 18 e 82 anos, aproximadamente.
            "DATANASCIMENTO": dia(p_hoje, -p_rng.randint(6570, 30000)),
            "CONVENIO": p_rng.choice(p_clinica["convenios"]),
            "CIDADE": loc_cidade,
            "ESTADO": loc_uf,
            # Cadastro entre ~4 anos atras e ~2 meses atras.
            "DATACADASTRO": dia(p_hoje, -p_rng.randint(60, 1500)),
        })

    return loc_registros


# ------------------------------------------  procedimentos
def gerar_procedimentos(p_clinica):
    """
    Catalogo fixo (nao sorteado): os codigos sao os mesmos nas duas clinicas,
    so o preco de tabela muda pelo `fator_tabela` do perfil. E o que permite
    ao admin comparar "o mesmo procedimento" entre os dois clientes.
    """
    loc_fator = p_clinica["fator_tabela"]

    return [
        {
            "ID": loc_id,
            "DESCRICAO": loc_desc,
            "ESPECIALIDADE": loc_esp,
            "VALORTABELA": round(loc_valor * loc_fator, 2),
            "DURACAOMINUTOS": loc_min,
        }
        for loc_id, loc_desc, loc_esp, loc_valor, loc_min in L_PROCEDIMENTOS
    ]


# ------------------------------------------  Janela de vencimentos
def sortear_vencimento(p_rng, p_faixa):
    """
    Deslocamento em dias, por faixa. As quatro faixas existem para que as
    perguntas do roteiro tenham resposta nao-vazia em qualquer dia:

      vencido_antigo  -> repasse atrasado ha mais de 30 dias (o manual manda
                         abrir recurso de glosa)
      vencido_recente -> atraso dentro da tolerancia
      proximos_30     -> "o que a clinica recebe este mes"
      futuro          -> carteira a receber
    """
    if p_faixa == "vencido_antigo":
        return -p_rng.randint(31, 150)
    if p_faixa == "vencido_recente":
        return -p_rng.randint(1, 30)
    if p_faixa == "proximos_30":
        return p_rng.randint(0, 30)
    return p_rng.randint(31, 120)


def sortear_status(p_rng, p_faixa):
    """
    Situacao coerente com a faixa de vencimento. Os codigos sao os do ERP
    Vitalis (1 em aberto, 2 recebido, 3 glosado, 9 cancelado) -- o significado
    NAO viaja nos dados, esta na semantica do TENANT.
    """
    if p_faixa == "vencido_antigo":
        # Repasse atrasado ha muito tempo geralmente virou glosa.
        return p_rng.choices([3, 2, 9], weights=[55, 35, 10])[0]
    if p_faixa == "vencido_recente":
        return p_rng.choices([1, 3, 2], weights=[45, 30, 25])[0]
    if p_faixa == "proximos_30":
        return p_rng.choices([1, 2, 9], weights=[85, 12, 3])[0]
    return p_rng.choices([1, 9], weights=[96, 4])[0]


def calcular_saldo(p_rng, p_valor, p_status):
    """
    Recebido (2) e cancelado (9) nao tem saldo. Em aberto (1) e glosado (3)
    mantem o saldo -- com uma minoria de repasse parcial, para que SALDO <
    VALOR apareca e o agente tenha que escolher a coluna certa.
    """
    if p_status in (2, 9):
        return 0.0
    if p_rng.random() < 0.15:
        return round(p_valor * p_rng.uniform(0.2, 0.8), 2)
    return p_valor


def faixas(p_quantidade):
    """
    Distribuicao das faixas de vencimento: 20% vencido antigo, 17% vencido
    recente, 30% proximos 30 dias, 33% futuro.
    """
    loc_antigo = int(p_quantidade * 0.20)
    loc_recente = int(p_quantidade * 0.17)
    loc_proximo = int(p_quantidade * 0.30)
    loc_futuro = p_quantidade - loc_antigo - loc_recente - loc_proximo

    return (["vencido_antigo"] * loc_antigo + ["vencido_recente"] * loc_recente
            + ["proximos_30"] * loc_proximo + ["futuro"] * loc_futuro)


# ------------------------------------------  faturamento
def gerar_faturamento(p_rng, p_hoje, p_clinica, p_pacientes, p_procedimentos):
    """
    O prazo entre atendimento e vencimento segue o rito escrito em
    `manual-operacional.md`: 60 dias para convenio, 0 (a vista) para
    Particular. Coerencia dado <-> documento e o que torna o cruzamento
    verificavel pelo agente.
    """
    loc_faixas = faixas(L_QTD_FATURAMENTO)
    p_rng.shuffle(loc_faixas)

    loc_por_id = {loc_p["ID"]: loc_p for loc_p in p_procedimentos}
    loc_registros = []

    for loc_i in range(L_QTD_FATURAMENTO):
        loc_paciente = p_rng.choice(p_pacientes)
        loc_proc = p_rng.choice(p_procedimentos)
        loc_faixa = loc_faixas[loc_i]

        loc_offset_venc = sortear_vencimento(p_rng, loc_faixa)
        loc_convenio = loc_paciente["CONVENIO"]
        loc_prazo = 0 if loc_convenio == "Particular" else 60
        loc_status = sortear_status(p_rng, loc_faixa)

        # O valor faturado orbita o preco de tabela do procedimento (co-
        # participacao, pacote, desconto), nunca e igual a ele em 100% dos
        # casos -- senao a pergunta "quanto foi faturado?" seria respondivel
        # so pelo catalogo, sem olhar o faturamento.
        loc_tabela = loc_por_id[loc_proc["ID"]]["VALORTABELA"]
        loc_valor = dinheiro(p_rng, loc_tabela * 0.85, loc_tabela * 1.25)

        loc_registros.append({
            "ID": p_clinica["base_guia"] + loc_i + 1,
            "PACIENTE_ID": loc_paciente["ID"],
            "PROCEDIMENTO_ID": loc_proc["ID"],
            "GUIA": "G%06d" % p_rng.randint(10000, 999999),
            "CONVENIO": loc_convenio,
            "DATAATENDIMENTO": dia(p_hoje, loc_offset_venc - loc_prazo),
            "DATAVENCIMENTO": dia(p_hoje, loc_offset_venc),
            "VALOR": loc_valor,
            "SALDO": calcular_saldo(p_rng, loc_valor, loc_status),
            "STATUS_ID": loc_status,
        })

    return loc_registros


# ------------------------------------------  Montagem de uma clinica
def gerar_clinica(p_clinica, p_hoje):
    """Os tres datasets de uma clinica, na ordem de dependencia."""
    loc_rng = random.Random(p_clinica["seed"])

    loc_pacientes = gerar_pacientes(loc_rng, p_hoje, p_clinica)
    loc_procedimentos = gerar_procedimentos(p_clinica)
    loc_faturamento = gerar_faturamento(
        loc_rng, p_hoje, p_clinica, loc_pacientes, loc_procedimentos
    )

    return [
        ("pacientes", loc_pacientes),
        ("procedimentos", loc_procedimentos),
        ("faturamento", loc_faturamento),
    ]


# ------------------------------------------  Envio
def enviar(p_nome, p_registros, p_meta, p_key, p_url, p_timeout, p_chunk, p_sync):
    """
    Envia um dataset como UMA CARGA, delegando a `enviar_dataset` de
    `contextia_client.py` -- que quebra em lotes, repete o `sync` em todos,
    marca `complete: true` so no ultimo e, se algum lote falhar, NAO fecha a
    carga (nada e removido).

    Os numeros do resumo vem da RESPOSTA da API (bloco `dataset`), nao de
    contagem local: e isso que prova a idempotencia e o ciclo de carga.

    Devolve o resumo, ou None se algum lote falhou.
    """
    loc_res = enviar_dataset(
        p_nome, p_registros,
        p_key=p_meta["key"],
        p_description=p_meta["description"],
        p_fields=p_meta["fields"],
        p_chunk=p_chunk,
        p_api_key=p_key,
        p_url=p_url,
        p_timeout=p_timeout,
        p_sync=p_sync,
    )

    if loc_res["lotes_erro"]:
        print("  [FALHA] dataset=%s: %d de %d lote(s) falharam. A carga NAO foi"
              " fechada -- nenhum registro removido."
              % (p_nome, loc_res["lotes_erro"], loc_res["lotes"]), file=sys.stderr)
        return None

    return loc_res


# ------------------------------------------  Dry-run: so os envelopes
def imprimir_envelopes(p_nome, p_registros, p_meta, p_chunk, p_sync):
    """
    Imprime o ESQUELETO de cada lote da carga (sem os registros, que sao
    muitos) mais o primeiro registro do dataset -- a prova visual do contrato:
    `sync` igual em todos os lotes e `complete` presente APENAS no ultimo.
    """
    loc_lotes = fatiar(p_registros, p_chunk)

    print("  --- %s (%d registros -> %d lote(s)) ---"
          % (p_nome, len(p_registros), len(loc_lotes)))

    for loc_i, loc_lote in enumerate(loc_lotes):
        loc_envelope = montar_envelope(
            p_nome, loc_lote,
            p_key=p_meta["key"],
            p_description=p_meta["description"],
            p_fields=p_meta["fields"],
            p_sync=p_sync,
            p_complete=(loc_i == len(loc_lotes) - 1),
        )

        loc_esqueleto = dict(loc_envelope)
        loc_esqueleto["description"] = "<...>"
        loc_esqueleto["fields"] = "<%d campos>" % len(p_meta["fields"])
        loc_esqueleto["records"] = "<%d registros>" % len(loc_lote)

        print("  lote %d/%d: %s" % (
            loc_i + 1, len(loc_lotes),
            json.dumps(loc_esqueleto, ensure_ascii=False),
        ))

    if p_registros:
        print("  registro[0]: %s" % json.dumps(p_registros[0], ensure_ascii=False))


# ------------------------------------------  Resolucao das chaves
def resolver_chave(p_apelido, p_args):
    """
    Chave de destino de uma clinica: --key-<apelido> vence a variavel de
    ambiente do perfil. Sem nenhuma das duas, devolve None -- a clinica e
    PULADA com aviso, em vez de o script tentar enviar com o placeholder e
    receber um 401 obscuro.
    """
    loc_arg = getattr(p_args, "key_" + p_apelido, None)

    if loc_arg and loc_arg != L_KEY_PLACEHOLDER:
        return loc_arg

    loc_env = os.environ.get(L_CLINICAS[p_apelido]["env"], "").strip()

    return loc_env or None


# ------------------------------------------  Execucao
def main():
    loc_parser = argparse.ArgumentParser(
        description="Popula os projetos do tenant erp-vitalis com o cenario "
                    "multi-tenant (duas clinicas)."
    )
    loc_parser.add_argument("--clinica", choices=["aurora", "bemestar", "ambas"],
                            default="ambas", help="qual cliente final carregar")
    loc_parser.add_argument("--key-aurora", default=None,
                            help="API key do projeto clinica-aurora "
                                 "(ou variavel CONTEXTIA_KEY_AURORA)")
    loc_parser.add_argument("--key-bemestar", default=None,
                            help="API key do projeto clinica-bemestar "
                                 "(ou variavel CONTEXTIA_KEY_BEMESTAR)")
    loc_parser.add_argument("--url", default=L_INTAKE_URL,
                            help="URL do endpoint de intake JSON")
    loc_parser.add_argument("--timeout", type=int, default=L_TIMEOUT)
    loc_parser.add_argument("--chunk", type=int, default=L_CHUNK)
    loc_parser.add_argument("--limite", type=int, default=0,
                            help="envia so os N primeiros registros de cada dataset "
                                 "(demonstra a remocao: o resto sai da view)")
    loc_parser.add_argument("--dry-run", action="store_true",
                            help="imprime os envelopes de cada lote, sem enviar")
    loc_args = loc_parser.parse_args()

    loc_hoje = datetime.date.today()

    loc_alvos = (["aurora", "bemestar"] if loc_args.clinica == "ambas"
                 else [loc_args.clinica])

    print("Cenario multi-tenant -- ERP Vitalis (dados ficticios)")
    print("Destino ..: %s" % loc_args.url)
    print("Data base : %s (vencimentos ancorados nela)" % loc_hoje.isoformat())
    if loc_args.limite > 0:
        print("LIMITE ...: %d registros por dataset -- o restante sera REMOVIDO"
              % loc_args.limite)
    print("")

    loc_resumos = []
    loc_falhas = 0
    loc_pulados = []

    for loc_apelido in loc_alvos:
        loc_clinica = L_CLINICAS[loc_apelido]
        loc_meta = montar_datasets(loc_clinica)
        loc_datasets = gerar_clinica(loc_clinica, loc_hoje)

        if loc_args.limite > 0:
            loc_datasets = [(loc_n, loc_r[:loc_args.limite])
                            for loc_n, loc_r in loc_datasets]

        # Um `sync` por CLINICA: sao cargas de projetos diferentes, e um
        # identificador compartilhado so embaralharia a auditoria dos dois.
        loc_sync = gerar_sync()

        if loc_args.dry_run:
            print("=== %s (projeto %s) | carga %s ==="
                  % (loc_clinica["nome"], loc_clinica["projeto"], loc_sync))
            for loc_nome, loc_registros in loc_datasets:
                imprimir_envelopes(loc_nome, loc_registros, loc_meta[loc_nome],
                                   loc_args.chunk, loc_sync)
            print("")
            continue

        loc_key = resolver_chave(loc_apelido, loc_args)

        if not loc_key:
            print("[PULADO] %s: sem chave. Passe --key-%s=ctx_... ou exporte %s."
                  % (loc_clinica["nome"], loc_apelido, loc_clinica["env"]),
                  file=sys.stderr)
            loc_pulados.append(loc_clinica["nome"])
            continue

        print("=== %s (projeto %s) ===" % (loc_clinica["nome"], loc_clinica["projeto"]))
        print("Chave ....: %s..." % loc_key[:12])
        print("Carga ....: %s" % loc_sync)

        for loc_nome, loc_registros in loc_datasets:
            print("Enviando %s (%d registros) ..." % (loc_nome, len(loc_registros)))
            loc_resumo = enviar(loc_nome, loc_registros, loc_meta[loc_nome],
                                loc_key, loc_args.url, loc_args.timeout,
                                loc_args.chunk, loc_sync)

            if loc_resumo is None:
                loc_falhas += 1
                continue

            loc_resumo["clinica"] = loc_clinica["nome"]
            loc_resumos.append(loc_resumo)

        print("")

    if loc_args.dry_run:
        return 0

    if loc_resumos:
        print("| clínica            | dataset       | view                      | novos | atualiz. | iguais | removidos | ressusc. | ativos |")
        print("|--------------------|---------------|---------------------------|-------|----------|--------|-----------|----------|--------|")
        for loc_r in loc_resumos:
            print("| %-18s | %-13s | %-25s | %5s | %8s | %6s | %9s | %8s | %6s |" % (
                loc_r["clinica"], loc_r["dataset"], loc_r["view"] or "-",
                loc_r["inserted"], loc_r["updated"], loc_r["duplicates"],
                loc_r["removed"], loc_r["resurrected"],
                loc_r["active_count"] if loc_r["active_count"] is not None else "-",
            ))
        print("")

    loc_removidos = sum(loc_r["removed"] for loc_r in loc_resumos)

    print("Numeros acima vem da RESPOSTA da API (bloco `dataset`), nao da contagem local.")
    print("Na PRIMEIRA execucao tudo cai em `novos`. Reexecutando no mesmo dia, tudo cai")
    print("em `iguais`; num dia diferente as datas mudam e as mesmas guias aparecem em")
    print("`atualiz.`. O upsert por `key: ID` garante que o cenario nunca duplique.")
    if loc_removidos:
        print("")
        print("ATENCAO: %s registro(s) removidos nesta carga." % loc_removidos)
    if loc_pulados:
        print("")
        print("NAO CARREGADO: %s (sem chave)." % ", ".join(loc_pulados))
    print("")
    print("Falta a parte de CONTEXTO -- semantica do tenant, sobrescrita da Aurora e os")
    print("dois documentos de storage/demo-vitalis/. Rode tools/setup-multitenant.sh")
    print("(ver docs/CENARIO-MULTITENANT.md).")

    return 1 if (loc_falhas or loc_pulados) else 0


if __name__ == "__main__":
    sys.exit(main())
