[CmdletBinding()]
param(
    [string]$Url = "",
    [string]$Token = "",

    [ValidateRange(15, 3600)]
    [int]$IntervaloSegundos = 30
)

Set-StrictMode -Version 2.0
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)

$nombreServicio = "AgenteMonitoreo"
$directorioPrograma = Join-Path $env:ProgramFiles "AgenteMonitoreo"
$directorioDatos = Join-Path $env:ProgramData "AgenteMonitoreo"
$destinoExe = Join-Path $directorioPrograma "AgenteMonitoreo.exe"
$rutaConfig = Join-Path $directorioDatos "config.json"
$rutaLog = Join-Path $directorioDatos "agente.log"
$directorioScript = Split-Path -Parent $MyInvocation.MyCommand.Definition
$origenExe = $null
$origenModerno = Join-Path `
    $directorioScript `
    "AgenteMonitoreo-Windows-Moderno.exe"
$origenLegacy = Join-Path `
    $directorioScript `
    "AgenteMonitoreo-Windows-Legacy.exe"
$rutaServidor = Join-Path $directorioScript "servidor.txt"

function Mostrar-Titulo {
    Clear-Host
    Write-Host "==================================================" -ForegroundColor DarkBlue
    Write-Host "             AGENTEMONITOREO" -ForegroundColor Blue
    Write-Host "        Instalador del servicio Windows" -ForegroundColor DarkGray
    Write-Host "==================================================" -ForegroundColor DarkBlue
    Write-Host ""
}

function Esta-Vacio {
    param([object]$Valor)

    if ($null -eq $Valor) {
        return $true
    }

    return ([string]$Valor).Trim().Length -eq 0
}

function Confirmar-Administrador {
    $identidad = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identidad)

    if (-not $principal.IsInRole(
        [Security.Principal.WindowsBuiltInRole]::Administrator
    )) {
        throw "Ejecuta Instalar-AgenteMonitoreo.cmd como administrador."
    }
}

function Seleccionar-Binario {
    $sistema = Get-WmiObject Win32_OperatingSystem
    $versionSistema = [Version]$sistema.Version

    if ($versionSistema.Major -lt 10) {
        $tipo = "Legacy"
        $ruta = $origenLegacy
    }
    else {
        $tipo = "Moderno"
        $ruta = $origenModerno
    }

    if (-not (Test-Path $ruta)) {
        throw "No se encontro el binario $tipo junto al instalador."
    }

    return @{
        Ruta = $ruta
        Tipo = $tipo
        Sistema = [string]$sistema.Caption
        VersionSistema = [string]$sistema.Version
    }
}

function Obtener-Url {
    param([string]$Valor)

    if ((Esta-Vacio $Valor) -and (Test-Path $rutaServidor)) {
        $Valor = [IO.File]::ReadAllText($rutaServidor)
    }

    if (Esta-Vacio $Valor) {
        $Valor = Read-Host "Direccion del servidor de monitoreo"
    }

    $Valor = $Valor.Trim().TrimEnd("/")

    if (-not $Valor.EndsWith("/agente/metricas")) {
        $Valor += "/agente/metricas"
    }

    $uriServidor = $null
    $esValida = [Uri]::TryCreate(
        $Valor,
        [UriKind]::Absolute,
        [ref]$uriServidor
    )

    if (-not $esValida -or
        @("http", "https") -notcontains $uriServidor.Scheme) {
        throw "La direccion del servidor no es valida."
    }

    return @{
        Url = $Valor
        Uri = $uriServidor
    }
}

function Probar-Servidor {
    param([Uri]$Uri)

    Write-Host (
        "Comprobando conexion con {0}:{1}..." -f
        $Uri.Host,
        $Uri.Port
    ) -ForegroundColor Cyan

    $clienteTcp = New-Object System.Net.Sockets.TcpClient

    try {
        $resultado = $clienteTcp.BeginConnect(
            $Uri.Host,
            $Uri.Port,
            $null,
            $null
        )

        if (-not $resultado.AsyncWaitHandle.WaitOne(10000, $false)) {
            throw "Tiempo de espera agotado."
        }

        $clienteTcp.EndConnect($resultado)
    }
    catch {
        throw (
            "No se pudo conectar con el servidor en {0}:{1}. {2}" -f
            $Uri.Host,
            $Uri.Port,
            $_.Exception.Message
        )
    }
    finally {
        $clienteTcp.Close()
    }

    Write-Host "Servidor accesible." -ForegroundColor Green
}

function Obtener-Token {
    param([string]$Valor)

    if (-not (Esta-Vacio $Valor)) {
        return $Valor.Trim()
    }

    $tokenSeguro = Read-Host `
        "Pega el token con clic derecho y presiona Enter" `
        -AsSecureString

    $puntero = [Runtime.InteropServices.Marshal]::SecureStringToBSTR(
        $tokenSeguro
    )

    try {
        return (
            [Runtime.InteropServices.Marshal]::PtrToStringBSTR(
                $puntero
            )
        ).Trim()
    }
    finally {
        [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($puntero)
    }
}

function Eliminar-Tareas-Anteriores {
    $programador = New-Object -ComObject "Schedule.Service"
    $programador.Connect()
    $carpetaRaiz = $programador.GetFolder("\")

    $nombresTareas = @(
        "AgenteMonitoreo",
        "Conectados IT - Agente de Monitoreo"
    )

    foreach ($nombreTarea in $nombresTareas) {
        $tareaAnterior = $null

        try {
            $tareaAnterior = $carpetaRaiz.GetTask(
                $nombreTarea
            )
        }
        catch {
            $tareaAnterior = $null
        }

        if ($null -ne $tareaAnterior) {
            try {
                $tareaAnterior.Stop(0)
            }
            catch {
            }

            $carpetaRaiz.DeleteTask(
                $nombreTarea,
                0
            )
        }
    }
}


function Detener-Version-Anterior {
    Eliminar-Tareas-Anteriores

    $servicio = Get-Service `
        -Name $nombreServicio `
        -ErrorAction SilentlyContinue

    if ($servicio) {
        Write-Host "Deteniendo servicio anterior..." `
            -ForegroundColor Yellow

        if ($servicio.Status -ne "Stopped") {
            Stop-Service -Name $nombreServicio -Force
            $servicio.WaitForStatus(
                [ServiceProcess.ServiceControllerStatus]::Stopped,
                [TimeSpan]::FromSeconds(20)
            )
        }

        & sc.exe delete $nombreServicio | Out-Null
        Start-Sleep -Seconds 2
    }

    @(
        "AgenteMonitoreo",
        "ConectadosIT-Agent"
    ) |
        ForEach-Object {
            Get-Process -Name $_ -ErrorAction SilentlyContinue |
                Stop-Process -Force
        }

    Start-Sleep -Seconds 1
}

function Instalar-Archivos {
    param(
        [string]$UrlFinal,
        [string]$TokenFinal
    )

    New-Item -ItemType Directory -Force -Path $directorioPrograma |
        Out-Null
    New-Item -ItemType Directory -Force -Path $directorioDatos |
        Out-Null

    Copy-Item -Force $origenExe $destinoExe

    [void][Reflection.Assembly]::LoadWithPartialName(
        "System.Web.Extensions"
    )

    $serializador = New-Object `
        System.Web.Script.Serialization.JavaScriptSerializer

    $configuracion = @{
        url = $UrlFinal
        token = $TokenFinal
        intervalo_segundos = $IntervaloSegundos
        timeout_segundos = 15
    }

    $json = $serializador.Serialize($configuracion)
    $rutaTemporal = "$rutaConfig.tmp"
    $utf8SinBom = New-Object System.Text.UTF8Encoding($false)

    [IO.File]::WriteAllText($rutaTemporal, $json, $utf8SinBom)
    Move-Item -Force $rutaTemporal $rutaConfig

    & icacls.exe `
        $rutaConfig `
        /inheritance:r `
        /grant:r `
        "*S-1-5-18:(F)" `
        "*S-1-5-32-544:(F)" |
        Out-Null

    if ($LASTEXITCODE -ne 0) {
        throw "No se pudieron proteger los permisos de config.json."
    }
}

function Registrar-Servicio {
    $comandoServicio = '"{0}" -service -config "{1}"' -f `
        $destinoExe,
        $rutaConfig

    $resultado = ([wmiclass]"Win32_Service").Create(
        $nombreServicio,
        $nombreServicio,
        $comandoServicio,
        16,
        1,
        "Automatic",
        $false,
        "LocalSystem",
        $null,
        $null,
        $null,
        $null
    )

    if ($resultado.ReturnValue -ne 0) {
        throw (
            "No se pudo crear el servicio. Codigo WMI: {0}" -f
            $resultado.ReturnValue
        )
    }

    & sc.exe description `
        $nombreServicio `
        "Agente del Sistema de Monitoreo" |
        Out-Null

    & sc.exe failure `
        $nombreServicio `
        reset= 86400 `
        actions= restart/60000/restart/60000/restart/60000 |
        Out-Null

    & sc.exe failureflag $nombreServicio 1 | Out-Null
}

function Iniciar-Servicio {
    Start-Service -Name $nombreServicio

    $servicio = Get-Service -Name $nombreServicio
    $servicio.WaitForStatus(
        [ServiceProcess.ServiceControllerStatus]::Running,
        [TimeSpan]::FromSeconds(25)
    )
    $servicio.Refresh()

    if ($servicio.Status -ne "Running") {
        throw "El servicio no alcanzo el estado Running."
    }

    return $servicio
}

Mostrar-Titulo
Confirmar-Administrador

$seleccion = Seleccionar-Binario
$origenExe = $seleccion.Ruta

$versionAgente = (& $origenExe -version | Select-Object -First 1)

if ($LASTEXITCODE -ne 0 -or (Esta-Vacio $versionAgente)) {
    throw "No se pudo comprobar la version de AgenteMonitoreo.exe."
}

$datosServidor = Obtener-Url -Valor $Url
$Url = $datosServidor.Url

Write-Host "Version:  $versionAgente" -ForegroundColor DarkGray
Write-Host "Sistema:  $($seleccion.Sistema) $($seleccion.VersionSistema)" `
    -ForegroundColor DarkGray
Write-Host "Binario:  $($seleccion.Tipo)" -ForegroundColor DarkGray
Write-Host "Servidor: $Url" -ForegroundColor DarkGray
Probar-Servidor -Uri $datosServidor.Uri

$Token = Obtener-Token -Valor $Token

if ((Esta-Vacio $Token) -or $Token.Length -lt 20) {
    throw "El token esta vacio o es demasiado corto."
}

Write-Host ""
Write-Host "Instalando servicio..." -ForegroundColor Cyan

Detener-Version-Anterior
Instalar-Archivos -UrlFinal $Url -TokenFinal $Token
$Token = $null

Registrar-Servicio
$servicioInstalado = Iniciar-Servicio

Write-Host "Esperando la primera comunicacion..." `
    -ForegroundColor Cyan
Start-Sleep -Seconds 12

Write-Host ""
Write-Host "==================================================" -ForegroundColor DarkGreen
Write-Host "       AGENTEMONITOREO INSTALADO" -ForegroundColor Green
Write-Host "==================================================" -ForegroundColor DarkGreen
Write-Host "Version:       $versionAgente"
Write-Host "Servicio:      $nombreServicio"
Write-Host "Estado:        $($servicioInstalado.Status)"
Write-Host "Inicio:        Automatico"
Write-Host "Servidor:      $Url"
Write-Host "Programa:      $destinoExe"
Write-Host "Configuracion: $rutaConfig"
Write-Host "Log:           $rutaLog"
Write-Host ""

if (Test-Path $rutaLog) {
    $lineas = @(Get-Content $rutaLog | Select-Object -Last 8)

    $lineas | ForEach-Object {
        Write-Host $_
    }

    if ($lineas -match "telemetria enviada correctamente") {
        Write-Host ""
        Write-Host "Comunicacion verificada correctamente." `
            -ForegroundColor Green
    }
    else {
        Write-Host ""
        Write-Host (
            "El servicio esta iniciado, pero aun no confirmo " +
            "el primer envio. Revisa el log indicado."
        ) -ForegroundColor Yellow
    }
}
else {
    Write-Host (
        "El servicio esta iniciado. El log aparecera al enviar datos."
    ) -ForegroundColor Yellow
}
