"""
Verificacao de conectividade dos destinos externos usados pela implantacao.

Duas decisoes de projeto:

- **Ping nao decide.** ICMP e bloqueado por firewall com frequencia, e servidor que nao responde
  ping costuma ter internet normalmente. O que decide e a conexao TCP na porta que a implantacao
  usa de verdade. O ping entra como informacao complementar, para separar "rede sem saida" de
  "porta bloqueada".
- **Falha so quando nada responde.** Um destino inacessivel gera aviso, porque nem toda
  implantacao usa todos eles (com versao fixa, por exemplo, o registro Docker nao e consultado).
  Nenhum destino respondendo e o caso sem ambiguidade de maquina sem rede.

O teste TCP usa o redirecionamento `/dev/tcp` do bash, disponivel em qualquer distribuicao, para
nao exigir `nc`, `curl` ou `telnet` instalados no servidor do cliente.
"""

from lib.echo import echo


TIMEOUT_SEGUNDOS = 8


class DestinoDeRede():
    def __init__(self, nome, endereco, porta, usado_para):
        self.nome = nome
        self.endereco = endereco
        self.porta = porta
        self.usado_para = usado_para

    def __str__(self):
        return f"{self.endereco}:{self.porta}"


class ResultadoDeDestino():
    def __init__(self, destino, dns_ok=False, tcp_ok=False, detalhe=""):
        self.destino = destino
        self.dns_ok = dns_ok
        self.tcp_ok = tcp_ok
        self.detalhe = detalhe


def destinos_do_servidor_linux(registro_docker):
    """Destinos que o servidor Linux precisa alcancar para a implantacao funcionar."""

    endereco_registro, _, porta_registro = _separar_endereco_e_porta(registro_docker)

    return [
        DestinoDeRede(
            "Registro Docker",
            endereco_registro,
            porta_registro,
            "baixar as imagens dos sistemas"
        ),
        DestinoDeRede(
            "Docker Hub",
            "registry-1.docker.io",
            443,
            "baixar as imagens de base (nginx, mongo e afins)"
        ),
        DestinoDeRede(
            "Internet (google.com)",
            "google.com",
            443,
            "confirmar que o servidor tem saida para a internet"
        ),
    ]


def _separar_endereco_e_porta(registro, porta_padrao=443):
    texto = str(registro or "").strip()

    if ":" not in texto:
        return texto, texto, porta_padrao

    endereco, _, porta = texto.rpartition(":")

    if not porta.isdigit():
        return texto, texto, porta_padrao

    return endereco, endereco, int(porta)


def montar_comando_dns(endereco):
    return f"getent hosts {endereco} >/dev/null 2>&1"


def montar_comando_tcp(endereco, porta, timeout=TIMEOUT_SEGUNDOS):
    return (
        f"timeout {timeout} bash -c 'exec 3<>/dev/tcp/{endereco}/{porta}' >/dev/null 2>&1"
    )


def montar_comando_ping(endereco):
    return f"ping -c 1 -W 3 {endereco} >/dev/null 2>&1"


def verificar_destinos(executar_comando, destinos, timeout=TIMEOUT_SEGUNDOS):
    """
    Verifica cada destino usando `executar_comando(comando) -> codigo_retorno`.

    Recebe a funcao de execucao por parametro para servir tanto ao servidor Linux (via SSH) quanto
    a testes, sem depender de rede.
    """

    resultados = []

    for destino in destinos:
        if executar_comando(montar_comando_dns(destino.endereco)) != 0:
            resultados.append(ResultadoDeDestino(
                destino,
                detalhe="nome nao resolveu no DNS do servidor"
            ))
            continue

        if executar_comando(montar_comando_tcp(destino.endereco, destino.porta, timeout)) != 0:
            resultados.append(ResultadoDeDestino(
                destino,
                dns_ok=True,
                detalhe=f"DNS resolveu, mas a porta {destino.porta} nao respondeu em {timeout}s "
                        "(firewall ou proxy bloqueando?)"
            ))
            continue

        resultados.append(ResultadoDeDestino(destino, dns_ok=True, tcp_ok=True))

    return resultados


def montar_mensagem_sem_rede(resultados, ping_respondeu):
    lista = ", ".join([str(resultado.destino) for resultado in resultados])

    return (
        f"Servidor Linux sem conectividade de rede: nenhum destino da implantacao respondeu "
        f"({lista}). Verifique conexao, DNS, firewall e proxy do servidor antes de executar "
        f"novamente. ping google.com respondeu: {ping_respondeu}."
    )


def echoar_resultados(resultados, prefixo=""):
    for resultado in resultados:
        if resultado.tcp_ok:
            echo(f"{prefixo}OK    {resultado.destino.nome} ({resultado.destino})")
        else:
            echo(
                f"{prefixo}FALHA {resultado.destino.nome} ({resultado.destino}) - "
                f"{resultado.detalhe}. Usado para {resultado.destino.usado_para}",
                "warn"
            )
