param(
    [Parameter(Mandatory = $true)]
    [string]$Version,

    [Parameter(Mandatory = $true)]
    [string]$ZipPath,

    [string]$ReportsZipPath = "",

    [Parameter(Mandatory = $true)]
    [string]$TargetId,

    [string]$TargetsPath = ""
)

$ErrorActionPreference = "Stop"

function Write-Step {
    param([string]$Message)
    Write-Host "[INFO] $Message"
}

function Write-Success {
    param([string]$Message)
    Write-Host "[SUCCESS] $Message"
}

function Get-ToolPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FileName,

        [string[]]$FallbackPaths = @()
    )

    $localPath = Join-Path $PSScriptRoot $FileName
    if (Test-Path $localPath) { return $localPath }

    foreach ($path in $FallbackPaths) {
        if (Test-Path $path) { return $path }
    }

    $command = Get-Command $FileName -ErrorAction SilentlyContinue
    if ($command) { return $command.Source }

    throw "No se encontro $FileName. Copialo junto a la app o instalalo con PuTTY."
}

function Get-ZipEntryNames {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $zip = [System.IO.Compression.ZipFile]::OpenRead($Path)
    try {
        return @(
            $zip.Entries |
                Where-Object { -not [string]::IsNullOrWhiteSpace($_.FullName) } |
                ForEach-Object { $_.FullName.Replace("\", "/") } |
                Where-Object {
                    $_ -and
                    $_ -notmatch "^__MACOSX/" -and
                    $_ -notmatch "(^|/)\.DS_Store$"
                }
        )
    }
    finally {
        $zip.Dispose()
    }
}

function ConvertTo-ShellArg {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Value
    )

    $singleQuote = [string][char]39
    return $singleQuote + $Value.Replace($singleQuote, "$singleQuote\$singleQuote$singleQuote") + $singleQuote
}

function Invoke-Plink {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Command
    )

    & $script:Plink -ssh "$($script:Target.user)@$($script:Target.host)" -P $script:Target.port -pw $script:Target.password -batch $Command 2>&1
}

if ([string]::IsNullOrWhiteSpace($TargetsPath)) {
    $TargetsPath = Join-Path $PSScriptRoot "targets.json"
}

if (-not (Test-Path $TargetsPath)) {
    throw "No se encontro el archivo de destinos: $TargetsPath"
}

if (-not (Test-Path $ZipPath)) {
    throw "No se encontro el ZIP principal: $ZipPath"
}

$targetsConfig = Get-Content -Raw -Path $TargetsPath | ConvertFrom-Json
$targetMatches = @($targetsConfig.targets | Where-Object { $_.id -eq $TargetId })
if ($targetMatches.Count -eq 0) {
    throw "No existe el destino seleccionado: $TargetId"
}

$script:Target = $targetMatches[0]
$script:Plink = Get-ToolPath -FileName "plink.exe" -FallbackPaths @(
    "C:\Program Files\PuTTY\plink.exe",
    "C:\Program Files (x86)\PuTTY\plink.exe"
)
$pscp = Get-ToolPath -FileName "pscp.exe" -FallbackPaths @(
    "C:\Program Files\PuTTY\pscp.exe",
    "C:\Program Files (x86)\PuTTY\pscp.exe"
)

$zipFileName = Split-Path -Leaf $ZipPath
$reportesFolderName = "reportes_subidos_jasper"
$hasReportsZipPath = -not [string]::IsNullOrWhiteSpace($ReportsZipPath)

if ($hasReportsZipPath -and -not (Test-Path $ReportsZipPath)) {
    throw "No se encontro el ZIP de reportes: $ReportsZipPath"
}

Write-Step "Version indicada: $Version"
Write-Step "Destino: $($Target.name) ($($Target.host):$($Target.port))"
Write-Step "Validando estructura del ZIP principal..."

$zipEntryNames = Get-ZipEntryNames -Path $ZipPath
if ($zipEntryNames.Count -eq 0) {
    throw "El ZIP principal esta vacio: $ZipPath"
}

$topLevelNames = @(
    $zipEntryNames |
        ForEach-Object { ($_ -split "/", 2)[0] } |
        Where-Object { $_ } |
        Sort-Object -Unique
)

$topLevelFiles = @(
    $zipEntryNames |
        Where-Object { ($_ -notmatch "/") -and (-not $_.EndsWith("/")) }
)

$zipRootPrefix = $null
if (($topLevelNames.Count -eq 1) -and ($topLevelFiles.Count -eq 0)) {
    $zipRootPrefix = $topLevelNames[0]
    Write-Step "El ZIP contiene una carpeta raiz ($zipRootPrefix). Se desplegara su contenido."
}

$deployEntryNames = $zipEntryNames
if ($zipRootPrefix) {
    $rootPrefixPattern = "^" + [regex]::Escape($zipRootPrefix.TrimEnd("/")) + "/"
    $deployEntryNames = @(
        $zipEntryNames |
            Where-Object { $_ -match $rootPrefixPattern } |
            ForEach-Object { $_ -replace $rootPrefixPattern, "" } |
            Where-Object { $_ }
    )
}

$deployTopLevelFolders = @(
    $deployEntryNames |
        Where-Object { $_ -match "/" } |
        ForEach-Object { ($_ -split "/", 2)[0] } |
        Where-Object { $_ } |
        Sort-Object -Unique
)

if ($deployTopLevelFolders.Count -le 1) {
    throw "El contenido a desplegar debe tener mas de una carpeta en el nivel padre."
}

$hasReportesFolder = @(
    $deployEntryNames |
        Where-Object {
            $_ -eq "$reportesFolderName/" -or
            $_ -like "$reportesFolderName/*"
        }
).Count -gt 0

$needsReportesZip = -not $hasReportesFolder
$reportesZipExtractMode = "direct"
$reportesZipName = ""

if ($needsReportesZip) {
    if (-not $hasReportsZipPath) {
        Write-Step "El ZIP no incluye $reportesFolderName y no se selecciono ZIP auxiliar. Se continua sin reportes."
    }
    else {
        $reportesZipName = Split-Path -Leaf $ReportsZipPath
        $reportesEntryNames = Get-ZipEntryNames -Path $ReportsZipPath
        if ($reportesEntryNames.Count -eq 0) {
            throw "El ZIP de reportes esta vacio: $ReportsZipPath"
        }

        $reportesZipHasFolder = @(
            $reportesEntryNames |
                Where-Object {
                    $_ -eq "$reportesFolderName/" -or
                    $_ -like "$reportesFolderName/*"
                }
        ).Count -gt 0

        if (-not $reportesZipHasFolder) {
            $reportesZipExtractMode = "into-folder"
            Write-Step "El ZIP de reportes no contiene carpeta raiz. Se extraera dentro de $reportesFolderName."
        }
    }
}

$remoteUploadPath = $Target.remoteUploadPath
$remoteAppPath = $Target.remoteAppPath
$backupDir = $Target.backupDir
$jasperstarterPath = "$remoteAppPath/_lib/libraries/grp/composer/vendor/geekcom/phpjasper/bin/jasperstarter/bin/jasperstarter"
$remoteTempPath = "$remoteUploadPath/gt_nova_deploy_tmp_$Version"
$remoteZipPath = "$remoteUploadPath/$zipFileName"
$remoteReportesZipPath = if ($reportesZipName) { "$remoteUploadPath/$reportesZipName" } else { "" }
$remoteExtractSource = if ($zipRootPrefix) { "$remoteTempPath/$zipRootPrefix" } else { $remoteTempPath }

$remoteAppArg = ConvertTo-ShellArg $remoteAppPath
$remoteTempArg = ConvertTo-ShellArg $remoteTempPath
$remoteZipArg = ConvertTo-ShellArg $remoteZipPath
$remoteExtractSourceArg = ConvertTo-ShellArg $remoteExtractSource
$remoteReportesFolderArg = ConvertTo-ShellArg "$remoteAppPath/$reportesFolderName"
$remoteReportesZipArg = if ($remoteReportesZipPath) { ConvertTo-ShellArg $remoteReportesZipPath } else { "" }
$jasperstarterArg = ConvertTo-ShellArg $jasperstarterPath

Write-Step "Probando conexion SSH..."
$connectionCheck = Invoke-Plink -Command "echo SSH_OK"
if ($connectionCheck -notmatch "SSH_OK") {
    throw "No se pudo conectar por SSH. Salida: $connectionCheck"
}
Write-Success "Conexion SSH OK."

$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupFileName = "backup_gt_nova_${Version}_$timestamp.tar.gz"
$backupCommand = "mkdir -p $(ConvertTo-ShellArg $backupDir) && tar -czf $(ConvertTo-ShellArg "$backupDir/$backupFileName") -C /var/www/html $(Split-Path -Leaf $remoteAppPath) && echo BACKUP_OK"

Write-Step "Creando backup remoto..."
$backupOutput = Invoke-Plink -Command $backupCommand
if ($backupOutput -notmatch "BACKUP_OK") {
    throw "Error al crear backup remoto. Salida: $backupOutput"
}
Write-Success "Backup creado: $backupDir/$backupFileName"

Write-Step "Subiendo ZIP principal..."
& $pscp -P $Target.port -pw $Target.password $ZipPath "$($Target.user)@$($Target.host):$remoteUploadPath/" 2>&1 | ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0) {
    throw "Error al subir el ZIP principal."
}
Write-Success "ZIP principal subido."

$shouldUploadReportsZip = $needsReportesZip -and $hasReportsZipPath
if ($shouldUploadReportsZip) {
    Write-Step "Subiendo ZIP auxiliar de reportes..."
    & $pscp -P $Target.port -pw $Target.password $ReportsZipPath "$($Target.user)@$($Target.host):$remoteUploadPath/" 2>&1 | ForEach-Object { Write-Host $_ }
    if ($LASTEXITCODE -ne 0) {
        throw "Error al subir el ZIP de reportes."
    }
    Write-Success "ZIP de reportes subido."
}

$cmdReportes = ":"
if ($shouldUploadReportsZip -and $reportesZipExtractMode -eq "direct") {
    $cmdReportes = "sudo unzip -oq $remoteReportesZipArg -d $remoteAppArg"
}
elseif ($shouldUploadReportsZip -and $reportesZipExtractMode -eq "into-folder") {
    $cmdReportes = @"
sudo mkdir -p $remoteReportesFolderArg
sudo unzip -oq $remoteReportesZipArg -d $remoteReportesFolderArg
"@
}

Write-Step "Descomprimiendo y reemplazando aplicacion..."
$deployCommand = @"
set -e
sudo rm -rf $remoteTempArg
sudo mkdir -p $remoteTempArg $remoteAppArg
sudo unzip -q $remoteZipArg -d $remoteTempArg
sudo find $remoteAppArg -mindepth 1 -maxdepth 1 -exec rm -rf {} +
sudo cp -a $remoteExtractSourceArg/. $remoteAppArg/
$cmdReportes
sudo rm -rf $remoteTempArg
echo UNZIP_OK
"@

$deployOutput = Invoke-Plink -Command $deployCommand
if ($deployOutput -notmatch "UNZIP_OK") {
    throw "Error al descomprimir o reemplazar aplicacion. Salida: $deployOutput"
}
Write-Success "Aplicacion reemplazada."

$serviceRestartLines = @()
foreach ($service in @($Target.services)) {
    if (-not [string]::IsNullOrWhiteSpace($service)) {
        $serviceRestartLines += "sudo systemctl restart $(ConvertTo-ShellArg $service)"
    }
}

$cleanupReportes = if ($shouldUploadReportsZip) { "sudo rm -f $remoteReportesZipArg" } else { ":" }
$fixJasperstarterPermissions = ":"
if ($Target.host -eq "4.246.183.98") {
    $fixJasperstarterPermissions = @"
if [ -f $jasperstarterArg ]; then
  sudo chmod 755 $jasperstarterArg
  ls -l $jasperstarterArg
else
  echo JASPERSTARTER_NOT_FOUND
  exit 1
fi
"@
}

$postDeployCommand = @"
set -e
sudo chown -R www-data:www-data $remoteAppArg
sudo find $remoteAppArg -type d -exec chmod 755 {} \;
sudo find $remoteAppArg -type f -exec chmod 644 {} \;
$fixJasperstarterPermissions
$($serviceRestartLines -join "`n")
sudo rm -f $remoteZipArg
$cleanupReportes
echo DEPLOY_OK
"@

Write-Step "Aplicando permisos, reiniciando servicios y limpiando temporales..."
$postDeployOutput = Invoke-Plink -Command $postDeployCommand
if ($postDeployOutput -notmatch "DEPLOY_OK") {
    throw "El deploy reemplazo archivos, pero fallo el post-deploy. Salida: $postDeployOutput"
}

if ($Target.host -eq "4.246.183.98") {
    if ($postDeployOutput -notmatch "jasperstarter") {
        throw "El deploy finalizo, pero no se pudo verificar el permiso de jasperstarter. Salida: $postDeployOutput"
    }

    Write-Success "Permiso 755 aplicado a jasperstarter."
}

Write-Success "Deploy finalizado correctamente para version $Version en $($Target.name)."
