import-module -name $PSScriptRoot\..\dadosValidacao.psm1
. $PSScriptRoot\..\..\_echo.ps1

<#
Verifica se a maquina que esta executando a implantacao alcanca os servicos externos de que ela
depende, antes de qualquer alteracao no ambiente.

Ping (ICMP) e frequentemente bloqueado por firewall, e servidor sem resposta de ping costuma ter
internet normalmente. Por isso o que decide aqui e a conexao TCP na porta que a implantacao usa de
verdade; o ping entra apenas como informacao complementar no log.

Falha a validacao somente quando NENHUM destino responde, que e o caso sem ambiguidade de maquina
sem rede. Destino individual inacessivel gera aviso: uma implantacao com versoes fixas, por
exemplo, nao precisa do registro Docker.
#>

function global:mu.validador.conectividadeComInternet.obterDestinos() {
    @(
        [pscustomobject]@{ nome = "GitLab (codigo)";      endereco = "gitlab.microuniverso.com.br"; porta = 443;  usadoPara = "baixar o release (git ls-remote)" }
        [pscustomobject]@{ nome = "Registro Docker";      endereco = "gitlab.microuniverso.com.br"; porta = 5050; usadoPara = "descobrir e baixar as imagens dos sistemas" }
        [pscustomobject]@{ nome = "Download MicroUniverso"; endereco = "download.microuniverso.com.br"; porta = 80; usadoPara = "baixar instalador, atualizador e scripts" }
        [pscustomobject]@{ nome = "Internet (google.com)"; endereco = "google.com"; porta = 443; usadoPara = "confirmar que a maquina tem saida para a internet" }
    )
}

function global:mu.validador.conectividadeComInternet.testarDestino() {
    param(
        $destino,
        $timeoutMs = 5000
    )

    $resultado = [pscustomobject]@{
        destino = $destino
        dnsOk   = $false
        tcpOk   = $false
        detalhe = ""
    }

    try {
        $enderecosIp = [System.Net.Dns]::GetHostAddresses($destino.endereco)
        $resultado.dnsOk = $enderecosIp.Count -gt 0
    } catch {
        $resultado.detalhe = "nome nao resolveu no DNS ($($_.Exception.Message))"
        return $resultado
    }

    $cliente = New-Object System.Net.Sockets.TcpClient
    try {
        $conexao = $cliente.ConnectAsync($destino.endereco, $destino.porta)

        if (-not $conexao.Wait($timeoutMs)) {
            $resultado.detalhe = "DNS resolveu, mas a porta $($destino.porta) nao respondeu em $([int]($timeoutMs / 1000))s (firewall ou proxy bloqueando?)"
        } elseif ($conexao.IsFaulted) {
            $resultado.detalhe = "DNS resolveu, mas a conexao na porta $($destino.porta) foi recusada ($($conexao.Exception.InnerException.Message))"
        } else {
            $resultado.tcpOk = $true
        }
    } catch {
        $resultado.detalhe = "falha ao conectar na porta $($destino.porta): $($_.Exception.Message)"
    } finally {
        $cliente.Dispose()
    }

    return $resultado
}

function global:mu.validador.conectividadeComInternet.pingRespondeu() {
    param(
        $endereco
    )

    try {
        return [bool](Test-Connection -ComputerName $endereco -Count 1 -Quiet -ErrorAction Stop)
    } catch {
        return $false
    }
}

function global:mu.validador.conectividadeComInternet() {
    param(
        [Parameter(ValueFromPipeline)]
        $dadosValidacao
    )

    if ($dadosValidacao.configuracaoValida -eq $false) {
        return $dadosValidacao
    }

    _echo "Verificando conectividade com os servicos externos da implantacao"

    $resultados = @(
        mu.validador.conectividadeComInternet.obterDestinos |
            ForEach-Object { mu.validador.conectividadeComInternet.testarDestino $_ }
    )

    foreach ($resultado in $resultados) {
        if ($resultado.tcpOk) {
            _echo "  OK   $($resultado.destino.nome) ($($resultado.destino.endereco):$($resultado.destino.porta))"
        } else {
            _echo "  FALHA $($resultado.destino.nome) ($($resultado.destino.endereco):$($resultado.destino.porta)) - $($resultado.detalhe). Usado para $($resultado.destino.usadoPara)"
        }
    }

    $inacessiveis = @($resultados | Where-Object { -not $_.tcpOk })

    if ($inacessiveis.Count -eq 0) {
        return $dadosValidacao
    }

    # Ping so aqui, quando algo falhou: serve para separar "rede sem saida" de "porta bloqueada",
    # e nao vale gastar o tempo dele quando tudo respondeu.
    $pingRespondeu = mu.validador.conectividadeComInternet.pingRespondeu "google.com"

    _echo "  ping google.com respondeu: $pingRespondeu (ICMP bloqueado e comum e nao significa falta de internet)"

    if ($inacessiveis.Count -lt $resultados.Count) {
        _echo "[WARN] $($inacessiveis.Count) de $($resultados.Count) destinos inacessiveis. A implantacao continua, mas as etapas que dependem deles vao falhar."
        return $dadosValidacao
    }

    $listaDestinos = ($inacessiveis | ForEach-Object { "$($_.destino.endereco):$($_.destino.porta)" }) -join ", "

    $dadosValidacao.codigoErro = "E0010"
    $dadosValidacao.mensagemErro = "Sem conectividade de rede: nenhum dos destinos da implantacao respondeu ($listaDestinos). " +
        "Verifique a conexao do servidor, DNS, firewall e proxy antes de executar novamente. " +
        "ping google.com respondeu: $pingRespondeu."
    $dadosValidacao.configuracaoValida = $false

    return $dadosValidacao
}
