param(
    [string]$InstallDir = "C:\TMarketPOS",
    [int]$Port = 3030,
    [switch]$StartWithWindows,
    [switch]$InstallNodeIfMissing,
    [switch]$OpenFirewall,
    [switch]$StartNow,
    [switch]$NoShortcuts,
    [switch]$UpdateOnly,
    [string]$RestoreBackupPath = ""
)

$ErrorActionPreference = "Stop"
$PortableNodeVersion = "22.16.0"
$PortableNodeArch = "x64"

function Write-Step($Text) {
    Write-Host "[T Market Server] $Text" -ForegroundColor Cyan
}

function Test-Admin {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Test-CompatibleNode($NodePath) {
    if(!(Test-Path -LiteralPath $NodePath) -and $NodePath -ne "node") { return $false }
    try {
        & $NodePath -e "const [major,minor]=process.versions.node.split('.').map(Number); try{ require('node:sqlite'); }catch(e){ process.exit(2); } process.exit((major > 22 || (major === 22 && minor >= 5)) ? 0 : 1)"
        return $LASTEXITCODE -eq 0
    } catch {
        return $false
    }
}

function Find-Node {
    param([string]$InstallDir = "")
    $portableNode = if([string]::IsNullOrWhiteSpace($InstallDir)) { "" } else { Join-Path $InstallDir "runtime\node\node.exe" }
    $candidates = @(
        $portableNode,
        "node",
        "C:\Program Files\nodejs\node.exe",
        "C:\Program Files (x86)\nodejs\node.exe"
    ) | Where-Object { ![string]::IsNullOrWhiteSpace($_) }

    foreach($candidate in $candidates) {
        if(Test-CompatibleNode $candidate) {
            if($candidate -eq "node") {
                $cmd = Get-Command node -ErrorAction SilentlyContinue
                if($cmd) { return $cmd.Source }
            }
            return $candidate
        }
    }
    return $null
}

function Install-Node-WithWinget {
    $winget = Get-Command winget -ErrorAction SilentlyContinue
    if(!$winget) {
        throw "No encontre Node.js compatible ni winget para instalarlo automaticamente. Instala Node.js 22.5 o superior."
    }
    Write-Step "Instalando Node.js LTS con winget..."
    & winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements
    if($LASTEXITCODE -ne 0) {
        throw "winget no pudo instalar Node.js. Instala Node.js 22.5 o superior y vuelve a ejecutar el instalador."
    }
}

function Install-PortableNode {
    param([string]$InstallDir)
    if([string]::IsNullOrWhiteSpace($InstallDir)) { throw "Carpeta de instalacion requerida para Node portatil." }
    $runtimeDir = Join-Path $InstallDir "runtime"
    $nodeDir = Join-Path $runtimeDir "node"
    $nodeExe = Join-Path $nodeDir "node.exe"
    if(Test-CompatibleNode $nodeExe) { return $nodeExe }

    New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
    $zipName = "node-v$PortableNodeVersion-win-$PortableNodeArch.zip"
    $url = "https://nodejs.org/dist/v$PortableNodeVersion/$zipName"
    $downloadPath = Join-Path $runtimeDir $zipName
    $extractDir = Join-Path $runtimeDir "node-download"
    Remove-Item -LiteralPath $extractDir -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $nodeDir -Recurse -Force -ErrorAction SilentlyContinue

    Write-Step "Descargando Node.js portatil v$PortableNodeVersion para este programa..."
    Invoke-WebRequest -Uri $url -OutFile $downloadPath -UseBasicParsing -TimeoutSec 180
    Expand-Archive -LiteralPath $downloadPath -DestinationPath $extractDir -Force
    $expanded = Get-ChildItem -LiteralPath $extractDir -Directory | Select-Object -First 1
    if(!$expanded) { throw "No pude extraer Node.js portatil." }
    Move-Item -LiteralPath $expanded.FullName -Destination $nodeDir -Force
    Remove-Item -LiteralPath $extractDir -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $downloadPath -Force -ErrorAction SilentlyContinue

    if(!(Test-CompatibleNode $nodeExe)) {
        throw "Node.js portatil se instalo, pero no es compatible con node:sqlite."
    }
    return $nodeExe
}

function Get-Browser {
    $candidates = @(
        "$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
        "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
        "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe",
        "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe"
    )
    foreach($path in $candidates) {
        if(Test-Path -LiteralPath $path) { return $path }
    }
    foreach($name in @("chrome.exe", "msedge.exe")) {
        $cmd = Get-Command $name -ErrorAction SilentlyContinue
        if($cmd) { return $cmd.Source }
    }
    return $null
}


function Install-LocalWebLibraries($InstallDir) {
    $vendor = Join-Path $InstallDir "public\vendor"
    New-Item -ItemType Directory -Force -Path $vendor | Out-Null

    $libraries = @(
        @{ Name="tesseract.min.js"; Url="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"; MinBytes=40000 },
        @{ Name="worker.min.js"; Url="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/worker.min.js"; MinBytes=80000 },
        @{ Name="xlsx.full.min.js"; Url="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"; MinBytes=700000 },
        @{ Name="jspdf.umd.min.js"; Url="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"; MinBytes=250000 },
        @{ Name="JsBarcode.all.min.js"; Url="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"; MinBytes=40000 },
        @{ Name="lucide.min.js"; Url="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"; MinBytes=200000 }
    )

    Write-Step "Preparando librerias locales de la interfaz..."
    foreach($lib in $libraries) {
        $target = Join-Path $vendor $lib.Name
        $existingOk = (Test-Path -LiteralPath $target) -and ((Get-Item -LiteralPath $target).Length -ge $lib.MinBytes)
        if($existingOk) { continue }

        try {
            Write-Host "  Descargando $($lib.Name)..." -ForegroundColor DarkCyan
            Invoke-WebRequest -Uri $lib.Url -OutFile $target -UseBasicParsing -TimeoutSec 60
            $size = (Get-Item -LiteralPath $target).Length
            if($size -lt $lib.MinBytes) { throw "descarga incompleta ($size bytes)" }
        } catch {
            Write-Host "  Aviso: no se pudo descargar $($lib.Name). El programa usara internet como respaldo si hace falta." -ForegroundColor Yellow
            Remove-Item -LiteralPath $target -Force -ErrorAction SilentlyContinue
        }
    }
}

function New-Shortcut($Path, $TargetPath, $Arguments, $WorkingDirectory, $IconLocation = $null) {
    $shell = New-Object -ComObject WScript.Shell
    $shortcut = $shell.CreateShortcut($Path)
    $shortcut.TargetPath = $TargetPath
    $shortcut.Arguments = $Arguments
    $shortcut.WorkingDirectory = $WorkingDirectory
    if($IconLocation) { $shortcut.IconLocation = $IconLocation }
    $shortcut.Save()
}

function Stop-ServerOnPort($Port) {
    try {
        Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue |
            ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }
        Start-Sleep -Milliseconds 500
    } catch { }
}

function New-InstallerDataBackup($InstallDir) {
    $dataDir = Join-Path $InstallDir "data"
    $dbPath = Join-Path $dataDir "tmarket.sqlite"
    if(!(Test-Path -LiteralPath $dbPath)) { return "" }

    $backupRoot = Join-Path $InstallDir "backups"
    $stamp = Get-Date -Format "yyyyMMdd-HHmmss"
    $backupDir = Join-Path $backupRoot "pre-update\installer-backup-$stamp"
    New-Item -ItemType Directory -Force -Path (Join-Path $backupDir "data") | Out-Null
    Copy-Item -Path (Join-Path $dataDir "*") -Destination (Join-Path $backupDir "data") -Recurse -Force -ErrorAction SilentlyContinue

    $desktopBackupDir = Join-Path ([Environment]::GetFolderPath("Desktop")) "TMarketPOS-respaldos"
    New-Item -ItemType Directory -Force -Path $desktopBackupDir | Out-Null
    Copy-Item -LiteralPath $dbPath -Destination (Join-Path $desktopBackupDir "tmarket-before-update-$stamp.sqlite") -Force

    $backupDb = Join-Path $backupDir "data\tmarket.sqlite"
    if(!(Test-Path -LiteralPath $backupDb)) {
        throw "No pude crear respaldo antes de actualizar. No se modificara la instalacion."
    }

    $sourceInfo = Get-Item -LiteralPath $dbPath
    $backupInfo = Get-Item -LiteralPath $backupDb
    if($sourceInfo.Length -ne $backupInfo.Length -or $backupInfo.Length -lt 1024) {
        throw "El respaldo creado no coincide con la base original. No se modificara la instalacion."
    }

    $sourceHash = (Get-FileHash -LiteralPath $dbPath -Algorithm SHA256).Hash
    $backupHash = (Get-FileHash -LiteralPath $backupDb -Algorithm SHA256).Hash
    if($sourceHash -ne $backupHash) {
        throw "El respaldo creado no paso verificacion de integridad. No se modificara la instalacion."
    }

    $manifest = [ordered]@{
        createdAt = (Get-Date).ToString("s")
        reason = "Respaldo automatico antes de instalar/actualizar"
        installDir = $InstallDir
        sourceDb = $dbPath
        backupDb = $backupDb
        sizeBytes = $backupInfo.Length
        sha256 = $backupHash
        desktopCopy = (Join-Path $desktopBackupDir "tmarket-before-update-$stamp.sqlite")
    }
    $manifest | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $backupDir "manifest.json") -Encoding UTF8

    return $backupDir
}

function Restore-InstallerDataBackup($BackupDir, $InstallDir) {
    if([string]::IsNullOrWhiteSpace($BackupDir)) { return }
    $backupData = Join-Path $BackupDir "data"
    if(!(Test-Path -LiteralPath $backupData)) { return }
    Write-Host "[T Market Server] Restaurando datos desde respaldo preventivo..." -ForegroundColor Yellow
    $dataDir = Join-Path $InstallDir "data"
    New-Item -ItemType Directory -Force -Path $dataDir | Out-Null
    Remove-Item -LiteralPath (Join-Path $dataDir "tmarket.sqlite-wal") -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath (Join-Path $dataDir "tmarket.sqlite-shm") -Force -ErrorAction SilentlyContinue
    Copy-Item -Path (Join-Path $backupData "*") -Destination $dataDir -Recurse -Force -ErrorAction SilentlyContinue
}

function Write-InstallManifest($InstallDir, $Mode, $PreUpdateBackup, $Port, $Url, $NetworkUrl) {
    $manifest = [ordered]@{
        installedAt = (Get-Date).ToString("s")
        mode = $Mode
        version = "5.8.16.11-desbloqueo-revision-compras"
        port = $Port
        localUrl = $Url
        networkUrl = $NetworkUrl
        installDir = $InstallDir
        database = (Join-Path $InstallDir "data\tmarket.sqlite")
        preUpdateBackup = $PreUpdateBackup
        notes = "El instalador conserva data, licencia y respaldos. Si hubo base previa, se verifico respaldo antes de actualizar."
    }
    $manifest | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallDir "install-manifest.json") -Encoding UTF8
}

function Assert-InstalledFiles($InstallDir) {
    $required = @(
        (Join-Path $InstallDir "server.js"),
        (Join-Path $InstallDir "package.json"),
        (Join-Path $InstallDir "public\Tmarket Cortes 9.5.html"),
        (Join-Path $InstallDir "iniciar-servidor-tmarket.bat"),
        (Join-Path $InstallDir "abrir-tmarket-tpv.bat")
    )
    $missing = @($required | Where-Object { !(Test-Path -LiteralPath $_) })
    if($missing.Count -gt 0) {
        throw "La instalacion quedo incompleta. Faltan archivos: $($missing -join ', ')"
    }
}

function Get-InstallModeLabel($InstallDir, $UpdateOnly) {
    if($UpdateOnly) { return "Actualizacion" }
    if(Test-Path -LiteralPath (Join-Path $InstallDir "data\tmarket.sqlite")) { return "Actualizacion/Reparacion" }
    return "Instalacion nueva"
}

function Get-DataProtectionMessage($InstallDir) {
    $db = Join-Path $InstallDir "data\tmarket.sqlite"
    if(Test-Path -LiteralPath $db) {
        return "Se detecto una base existente. Se hara respaldo verificado antes de copiar archivos nuevos."
    }
    return "No se detecto base existente en esta carpeta. Se prepara instalacion limpia."
}

function Get-DatabaseHealth($DbPath, $NodePath) {
    $result = [ordered]@{
        Path = $DbPath
        Exists = $false
        Total = 0
        ZeroSale = 0
        ZeroCost = 0
        Sales = 0
        Score = -1
        Healthy = $false
        Suspicious = $false
        Error = ""
    }
    if(!(Test-Path -LiteralPath $DbPath)) { return [pscustomobject]$result }
    $result.Exists = $true
    try {
        $script = @"
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync(process.argv[1]);
const row = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN sale_price=0 THEN 1 ELSE 0 END) zeroSale, SUM(CASE WHEN cost_price=0 THEN 1 ELSE 0 END) zeroCost FROM products").get();
let sales = 0;
try { sales = db.prepare("SELECT COUNT(*) count FROM sales").get().count || 0; } catch {}
console.log(JSON.stringify({ total: row.total || 0, zeroSale: row.zeroSale || 0, zeroCost: row.zeroCost || 0, sales }));
db.close();
"@
        $json = & $NodePath -e $script $DbPath
        if($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($json)) { throw "Node no pudo leer la base." }
        $data = $json | ConvertFrom-Json
        $result.Total = [int]$data.total
        $result.ZeroSale = [int]$data.zeroSale
        $result.ZeroCost = [int]$data.zeroCost
        $result.Sales = [int]$data.sales
        $result.Healthy = $result.Total -gt 0 -and $result.ZeroSale -eq 0
        $result.Suspicious = $result.Total -gt 20 -and ($result.ZeroSale -gt [Math]::Max(5, [int]($result.Total * 0.10)))
        $result.Score = ($result.Total - $result.ZeroSale) + ($result.Sales * 100)
    } catch {
        $result.Error = $_.Exception.Message
    }
    return [pscustomobject]$result
}

function Find-BestExistingDatabase($InstallDir, $NodePath) {
    $backups = @()
    foreach($root in @((Join-Path $InstallDir "backups"))) {
        if(Test-Path -LiteralPath $root) {
            $backups += Get-ChildItem -LiteralPath $root -Recurse -Filter "*.sqlite" -File -ErrorAction SilentlyContinue |
                Sort-Object LastWriteTime -Descending |
                Select-Object -First 10 -ExpandProperty FullName
        }
    }
    $health = $backups | Select-Object -Unique | ForEach-Object { Get-DatabaseHealth $_ $NodePath }
    return $health | Where-Object { $_.Exists -and $_.Healthy } | Sort-Object Score -Descending | Select-Object -First 1
}

function Protect-ExistingDatabaseBeforeUpdate($InstallDir, $NodePath) {
    $targetDb = Join-Path $InstallDir "data\tmarket.sqlite"
    $targetHealth = Get-DatabaseHealth $targetDb $NodePath
    if(!$targetHealth.Exists) {
        Write-Host "[T Market Server] Instalacion limpia: no se encontro base existente en esta carpeta." -ForegroundColor Yellow
        Write-Host "[T Market Server] No se copiara ninguna base externa. Para migrar, usa la opcion restaurar respaldo." -ForegroundColor Yellow
        return
    }
    if($targetHealth.Exists -and -not $targetHealth.Suspicious) { return }

    $best = Find-BestExistingDatabase $InstallDir $NodePath
    if($best -and $best.Path -and $best.Path -ne $targetDb) {
        Write-Host "[T Market Server] La base destino se ve sospechosa. Usare un respaldo sano de esta misma instalacion:" -ForegroundColor Yellow
        Write-Host "  $($best.Path)" -ForegroundColor Yellow
        New-Item -ItemType Directory -Force -Path (Split-Path -Parent $targetDb) | Out-Null
        Remove-Item -LiteralPath "$targetDb-wal" -Force -ErrorAction SilentlyContinue
        Remove-Item -LiteralPath "$targetDb-shm" -Force -ErrorAction SilentlyContinue
        Copy-Item -LiteralPath $best.Path -Destination $targetDb -Force
        return
    }

    if($targetHealth.Suspicious) {
        throw "La base en $targetDb parece danada: $($targetHealth.ZeroSale) de $($targetHealth.Total) productos tienen precio venta en cero. No actualizare hasta restaurar un respaldo sano."
    }
}

function Resolve-BackupSqlite($BackupPath) {
    $clean = ($BackupPath -as [string]).Trim('"').Trim()
    if([string]::IsNullOrWhiteSpace($clean)) { return "" }
    if(!(Test-Path -LiteralPath $clean)) { throw "No encontre el respaldo: $clean" }

    $item = Get-Item -LiteralPath $clean
    if(!$item.PSIsContainer) {
        if($item.Extension -ieq ".sqlite" -or $item.Name -ieq "tmarket.sqlite") { return $item.FullName }
        if($item.Extension -ieq ".json") {
            $sibling = [IO.Path]::ChangeExtension($item.FullName, ".sqlite")
            if(Test-Path -LiteralPath $sibling) { return $sibling }
        }
        throw "El respaldo debe ser .sqlite, una carpeta data, o el .json generado junto a su .sqlite."
    }

    $direct = Join-Path $item.FullName "tmarket.sqlite"
    if(Test-Path -LiteralPath $direct) { return $direct }
    $nested = Get-ChildItem -LiteralPath $item.FullName -Recurse -Filter "tmarket.sqlite" -File -ErrorAction SilentlyContinue | Select-Object -First 1
    if($nested) { return $nested.FullName }
    $sqlite = Get-ChildItem -LiteralPath $item.FullName -Recurse -Filter "*.sqlite" -File -ErrorAction SilentlyContinue | Select-Object -First 1
    if($sqlite) { return $sqlite.FullName }
    throw "No encontre archivo .sqlite dentro de: $clean"
}

function Restore-ServerBackup($BackupPath, $InstallDir) {
    $sqlite = Resolve-BackupSqlite $BackupPath
    if([string]::IsNullOrWhiteSpace($sqlite)) { return }

    $dataDir = Join-Path $InstallDir "data"
    New-Item -ItemType Directory -Force -Path $dataDir | Out-Null
    Remove-Item -LiteralPath (Join-Path $dataDir "tmarket.sqlite-wal") -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath (Join-Path $dataDir "tmarket.sqlite-shm") -Force -ErrorAction SilentlyContinue
    Copy-Item -LiteralPath $sqlite -Destination (Join-Path $dataDir "tmarket.sqlite") -Force
    Write-Step "Respaldo restaurado: $sqlite"
}

function Get-PrimaryIPv4 {
    try {
        $route = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue |
            Sort-Object RouteMetric, InterfaceMetric |
            Select-Object -First 1
        if($route) {
            $ip = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $route.InterfaceIndex -ErrorAction SilentlyContinue |
                Where-Object { $_.IPAddress -notlike "127.*" -and $_.IPAddress -notlike "169.254.*" } |
                Select-Object -First 1 -ExpandProperty IPAddress
            if($ip) { return $ip }
        }
        $fallback = Get-NetIPAddress -AddressFamily IPv4 |
            Where-Object { $_.IPAddress -notlike "127.*" -and $_.IPAddress -notlike "169.254.*" -and $_.IPAddress -notlike "192.168.56.*" } |
            Select-Object -First 1 -ExpandProperty IPAddress
        if($fallback) { return $fallback }
    } catch {}
    return "IP-DE-ESTA-COMPUTADORA"
}

function Get-RecommendedReservedServerUrls($Port) {
    $ip = Get-PrimaryIPv4
    if($ip -notmatch "^\d+\.\d+\.\d+\.\d+$") { return @() }
    $dot = $ip.LastIndexOf(".")
    if($dot -le 0) { return @() }
    $prefix = $ip.Substring(0, $dot + 1)
    return @(90, 100, 150, 200, 59) | ForEach-Object { "http://$prefix${_}:$Port" }
}

function Enable-TMarketFirewallRule($Port) {
    $ruleName = "T Market POS Servidor $Port"
    $existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
    if($existing) {
        $existing | Set-NetFirewallRule -Enabled True -Direction Inbound -Action Allow -Profile Any | Out-Null
        $existing | Get-NetFirewallPortFilter | Set-NetFirewallPortFilter -Protocol TCP -LocalPort $Port | Out-Null
    } else {
        New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $Port -Profile Any | Out-Null
    }
}

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$payloadFiles = @("server.js", "package.json")
$payloadPublic = Join-Path $scriptDir "public"
$startMenuDir = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\T Market POS"
$programsRoot = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs"
$uninstallRegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\TMarketPOSServidor"

foreach($file in $payloadFiles) {
    if(!(Test-Path -LiteralPath (Join-Path $scriptDir $file))) {
        throw "Falta $file en la carpeta del instalador."
    }
}
if(!(Test-Path -LiteralPath (Join-Path $payloadPublic "Tmarket Cortes 9.5.html"))) {
    throw "Falta el HTML de T Market en la carpeta public."
}

$nodePath = Find-Node $InstallDir
if(!$nodePath -and $InstallNodeIfMissing) {
    try {
        Install-Node-WithWinget
        $nodePath = Find-Node $InstallDir
    } catch {
        Write-Host "[T Market Server] Aviso: winget no dejo Node compatible: $($_.Exception.Message)" -ForegroundColor Yellow
    }
    if(!$nodePath) {
        $nodePath = Install-PortableNode $InstallDir
    }
}
if(!$nodePath) {
    Write-Host "[T Market Server] No encontre Node.js compatible instalado. Preparando Node portatil del programa..." -ForegroundColor Yellow
    $nodePath = Install-PortableNode $InstallDir
}

Write-Step "Node.js compatible: $nodePath"

$installMode = Get-InstallModeLabel $InstallDir $UpdateOnly
Write-Host ""
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host " T Market POS Server - $installMode" -ForegroundColor Cyan
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host "Carpeta: $InstallDir"
Write-Host (Get-DataProtectionMessage $InstallDir) -ForegroundColor Green
Write-Host "Importante: productos, ventas, cortes, usuarios, licencia y respaldos se conservan." -ForegroundColor Green
Write-Host ""

Stop-ServerOnPort $Port

$preUpdateBackup = ""
try {
    Protect-ExistingDatabaseBeforeUpdate $InstallDir $nodePath
    $preUpdateBackup = New-InstallerDataBackup $InstallDir
    if($preUpdateBackup) {
        Write-Step "Respaldo verificado antes de actualizar:"
        Write-Host "  $preUpdateBackup" -ForegroundColor Green
    }

    New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
    New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir "public") | Out-Null
    New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir "data") | Out-Null
    New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir "logs") | Out-Null
    New-Item -ItemType Directory -Force -Path $startMenuDir | Out-Null

    Write-Step "Copiando archivos del programa sin tocar la base de datos..."
    Copy-Item -LiteralPath (Join-Path $scriptDir "server.js") -Destination (Join-Path $InstallDir "server.js") -Force
    Copy-Item -LiteralPath (Join-Path $scriptDir "package.json") -Destination (Join-Path $InstallDir "package.json") -Force
    Copy-Item -Path (Join-Path $payloadPublic "*") -Destination (Join-Path $InstallDir "public") -Recurse -Force
    Copy-Item -LiteralPath $PSCommandPath -Destination (Join-Path $InstallDir "instalar-o-reparar-servidor-tmarket.ps1") -Force -ErrorAction SilentlyContinue

    Install-LocalWebLibraries $InstallDir

    if(![string]::IsNullOrWhiteSpace($RestoreBackupPath)) {
        Restore-ServerBackup -BackupPath $RestoreBackupPath -InstallDir $InstallDir
    }
} catch {
    Restore-InstallerDataBackup $preUpdateBackup $InstallDir
    throw "La actualizacion se detuvo para evitar perdida de datos: $($_.Exception.Message)"
}

$startBat = Join-Path $InstallDir "iniciar-servidor-tmarket.bat"
$stopBat = Join-Path $InstallDir "detener-servidor-tmarket.bat"
$openBat = Join-Path $InstallDir "abrir-tmarket-tpv.bat"
$hiddenVbs = Join-Path $InstallDir "iniciar-servidor-oculto.vbs"
$firewallBat = Join-Path $InstallDir "habilitar-firewall-tmarket-admin.bat"
$diagnosticPs1 = Join-Path $InstallDir "diagnostico-servidor-tmarket.ps1"
$diagnosticBat = Join-Path $InstallDir "diagnostico-servidor-tmarket.bat"
$uninstallerPath = Join-Path $InstallDir "desinstalar-tmarket-servidor.ps1"
$url = "http://localhost:$Port"
$networkUrl = "http://$(Get-PrimaryIPv4):$Port"
$recommendedNetworkUrls = @(Get-RecommendedReservedServerUrls $Port)
$recommendedNetworkText = if($recommendedNetworkUrls.Count) { $recommendedNetworkUrls -join "`r`n" } else { "No se pudo calcular automaticamente. Usa una IP alta del mismo rango, por ejemplo .90 o .100." }
$browserPath = Get-Browser
$appIcon = Join-Path $InstallDir "public\tmarket-app.ico"

$startContent = @"
@echo off
cd /d "%~dp0"
set "TMARKET_PORT=$Port"
set "TMARKET_HOST=0.0.0.0"
set "TMARKET_DB=%~dp0data\tmarket.sqlite"
set "TMARKET_BACKUP_DIR=%~dp0backups"
set "TMARKET_HTML=%~dp0public\Tmarket Cortes 9.5.html"
set "TMARKET_LOGO=%~dp0public\tmarket-logo.jfif"
"$nodePath" --no-warnings server.js >> "%~dp0logs\server.log" 2>> "%~dp0logs\server.err.log"
"@
Set-Content -LiteralPath $startBat -Value $startContent -Encoding ASCII

$stopContent = @"
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id `$_.OwningProcess -Force -ErrorAction SilentlyContinue }"
echo Servidor T Market detenido si estaba activo.
pause
"@
Set-Content -LiteralPath $stopBat -Value $stopContent -Encoding ASCII

if($browserPath) {
    $openContent = @"
@echo off
cd /d "%~dp0"
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { `$r = Invoke-WebRequest -Uri '$url' -UseBasicParsing -TimeoutSec 2; if(`$r.StatusCode -ge 200){ exit 0 } } catch { exit 1 }"
if errorlevel 1 (
    start "" wscript.exe "%~dp0iniciar-servidor-oculto.vbs"
    ping 127.0.0.1 -n 4 >nul
)
start "" "$browserPath" --app="$url" --start-maximized
"@
} else {
    $openContent = @"
@echo off
cd /d "%~dp0"
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { `$r = Invoke-WebRequest -Uri '$url' -UseBasicParsing -TimeoutSec 2; if(`$r.StatusCode -ge 200){ exit 0 } } catch { exit 1 }"
if errorlevel 1 (
    start "" wscript.exe "%~dp0iniciar-servidor-oculto.vbs"
    ping 127.0.0.1 -n 4 >nul
)
start "" "$url"
"@
}
Set-Content -LiteralPath $openBat -Value $openContent -Encoding ASCII

$vbsContent = @"
Set shell = CreateObject("WScript.Shell")
shell.Run Chr(34) & "$startBat" & Chr(34), 0, False
"@
Set-Content -LiteralPath $hiddenVbs -Value $vbsContent -Encoding ASCII

$firewallContent = @"
@echo off
net session >nul 2>&1
if %errorlevel% neq 0 (
  powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
  exit /b
)
powershell -NoProfile -ExecutionPolicy Bypass -Command "`$rule = Get-NetFirewallRule -DisplayName 'T Market POS Servidor $Port' -ErrorAction SilentlyContinue; if(`$rule){ `$rule | Set-NetFirewallRule -Enabled True -Direction Inbound -Action Allow -Profile Any | Out-Null; `$rule | Get-NetFirewallPortFilter | Set-NetFirewallPortFilter -Protocol TCP -LocalPort $Port | Out-Null } else { New-NetFirewallRule -DisplayName 'T Market POS Servidor $Port' -Direction Inbound -Action Allow -Protocol TCP -LocalPort $Port -Profile Any | Out-Null }; Write-Host 'Firewall habilitado para T Market POS en puerto $Port.'"
pause
"@
Set-Content -LiteralPath $firewallBat -Value $firewallContent -Encoding ASCII

$diagnosticContent = @"
`$ErrorActionPreference = "Continue"
`$installDir = "$($InstallDir.Replace('"','`"'))"
`$port = $Port
`$nodePath = "$($nodePath.Replace('"','`"'))"
`$localUrl = "http://localhost:`$port"
`$networkUrl = "http://$(Get-PrimaryIPv4):$Port"

function Section(`$Text) {
    Write-Host ""
    Write-Host "==== `$Text ====" -ForegroundColor Cyan
}

Section "Diagnostico T Market POS Servidor"
Write-Host "Carpeta: `$installDir"
Write-Host "URL local: `$localUrl"
Write-Host "URL red:   `$networkUrl"

Section "Archivos"
foreach(`$path in @(
    (Join-Path `$installDir "server.js"),
    (Join-Path `$installDir "public\Tmarket Cortes 9.5.html"),
    (Join-Path `$installDir "data\tmarket.sqlite"),
    (Join-Path `$installDir "iniciar-servidor-tmarket.bat")
)) {
    if(Test-Path -LiteralPath `$path) {
        `$item = Get-Item -LiteralPath `$path
        Write-Host "OK  `$path  (`$(`$item.Length) bytes)" -ForegroundColor Green
    } else {
        Write-Host "FALTA `$path" -ForegroundColor Red
    }
}

Section "Node.js"
if(Test-Path -LiteralPath `$nodePath) {
    Write-Host "Node configurado: `$nodePath" -ForegroundColor Green
    & `$nodePath -v
    & `$nodePath -e "try{require('node:sqlite'); console.log('node:sqlite OK')}catch(e){console.error(e.message); process.exit(1)}"
} else {
    Write-Host "No encontre Node en: `$nodePath" -ForegroundColor Red
    Write-Host "Reinstala el servidor y responde SI cuando pregunte instalar Node.js con winget."
}

Section "Puerto"
`$listeners = @(Get-NetTCPConnection -LocalPort `$port -State Listen -ErrorAction SilentlyContinue)
if(`$listeners.Count -gt 0) {
    foreach(`$conn in `$listeners) {
        `$proc = Get-Process -Id `$conn.OwningProcess -ErrorAction SilentlyContinue
        Write-Host "Escuchando en `$(`$conn.LocalAddress):`$(`$conn.LocalPort) PID `$(`$conn.OwningProcess) `$(`$proc.ProcessName)" -ForegroundColor Green
    }
} else {
    Write-Host "No hay ningun proceso escuchando en el puerto `$port." -ForegroundColor Red
}

Section "Prueba HTTP"
try {
    `$health = Invoke-RestMethod -Uri "`$localUrl/api/health" -TimeoutSec 5
    if(`$health.ok -and `$health.app -eq "TMarketPOS") {
        Write-Host "Servidor responde correctamente en /api/health." -ForegroundColor Green
        `$health | ConvertTo-Json -Depth 4
    } else {
        Write-Host "Hay respuesta HTTP, pero no parece ser T Market POS." -ForegroundColor Yellow
        `$health | ConvertTo-Json -Depth 4
    }
} catch {
    Write-Host "No pude abrir `$localUrl/api/health" -ForegroundColor Red
    Write-Host `$_.Exception.Message
}

Section "Firewall"
try {
    `$rule = Get-NetFirewallRule -DisplayName "T Market POS Servidor `$port" -ErrorAction SilentlyContinue
    if(`$rule) {
        `$rule | Select-Object DisplayName,Enabled,Direction,Action,Profile | Format-Table -AutoSize
    } else {
        Write-Host "No encontre regla de firewall para T Market POS." -ForegroundColor Yellow
        Write-Host "Ejecuta como administrador: `$installDir\habilitar-firewall-tmarket-admin.bat"
    }
} catch {
    Write-Host "No pude leer Firewall: `$(`$_.Exception.Message)" -ForegroundColor Yellow
}

Section "Ultimos logs"
foreach(`$log in @((Join-Path `$installDir "logs\server.err.log"), (Join-Path `$installDir "logs\server.log"))) {
    Write-Host ""
    Write-Host `$log -ForegroundColor Gray
    if(Test-Path -LiteralPath `$log) {
        Get-Content -LiteralPath `$log -Tail 30
    } else {
        Write-Host "Sin log todavia."
    }
}

Write-Host ""
pause
"@
Set-Content -LiteralPath $diagnosticPs1 -Value $diagnosticContent -Encoding UTF8

$diagnosticBatContent = @"
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0diagnostico-servidor-tmarket.ps1"
"@
Set-Content -LiteralPath $diagnosticBat -Value $diagnosticBatContent -Encoding ASCII

Assert-InstalledFiles $InstallDir
Write-InstallManifest -InstallDir $InstallDir -Mode $installMode -PreUpdateBackup $preUpdateBackup -Port $Port -Url $url -NetworkUrl $networkUrl

$powerShellPath = (Get-Command powershell.exe).Source

if(!$NoShortcuts) {
    $desktop = [Environment]::GetFolderPath("Desktop")
    New-Shortcut -Path (Join-Path $desktop "T Market POS - Iniciar servidor.lnk") -TargetPath $startBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $desktop "T Market POS.lnk") -TargetPath $openBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $startMenuDir "T Market POS Servidor.lnk") -TargetPath $openBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $startMenuDir "Iniciar servidor T Market POS.lnk") -TargetPath $startBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $startMenuDir "Detener servidor T Market POS.lnk") -TargetPath $stopBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $startMenuDir "Diagnostico servidor T Market POS.lnk") -TargetPath $diagnosticBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $startMenuDir "Reparar instalacion T Market POS.lnk") -TargetPath $powerShellPath -Arguments "-NoProfile -ExecutionPolicy Bypass -File `"$InstallDir\instalar-o-reparar-servidor-tmarket.ps1`" -UpdateOnly -StartNow" -WorkingDirectory $InstallDir -IconLocation $appIcon
    New-Shortcut -Path (Join-Path $programsRoot "T Market POS Servidor.lnk") -TargetPath $openBat -Arguments "" -WorkingDirectory $InstallDir -IconLocation $appIcon
}

if($StartWithWindows) {
    schtasks /Create /SC ONLOGON /TN "TMarketPOSServer" /TR "wscript.exe `"$hiddenVbs`"" /F | Out-Null
    Write-Step "Inicio con Windows configurado."
}

if($OpenFirewall) {
    if(Test-Admin) {
        Enable-TMarketFirewallRule $Port
        Write-Step "Firewall habilitado para el puerto $Port."
    } else {
        Write-Host "[T Market Server] Para permitir otras PCs, ejecuta como administrador:" -ForegroundColor Yellow
        Write-Host $firewallBat -ForegroundColor Yellow
    }
}

if($StartNow) {
    Write-Step "Iniciando servidor..."
    Start-Process -FilePath "wscript.exe" -ArgumentList "`"$hiddenVbs`"" -WindowStyle Hidden
    Start-Sleep -Seconds 2
}

$readme = @"
T MARKET POS - SERVIDOR INSTALADO

Carpeta:
$InstallDir

URL en esta computadora:
$url

URL para otras computadoras:
$networkUrl

Recomendacion para evitar cambios cuando se reinicia el modem:
- En el modem/router reserva una IP alta para esta PC servidor.
- Preferida: usa terminacion .90 si esta libre.
- Alternativas recomendadas:
$recommendedNetworkText
- Si puedes, cambia el nombre de la PC servidor en Windows a: TMARKET-SERVIDOR.
- Despues instala los clientes apuntando primero a: http://tmarket-servidor:$Port
- Si el nombre no responde, usa la URL reservada alta, por ejemplo: $($recommendedNetworkUrls | Select-Object -First 1)
- Si tu modem permite DNS local, crea un alias: tmarket.local -> IP reservada del servidor.
- HTTPS/certificado local solo se recomienda para instalaciones avanzadas; requiere instalar certificado en cada PC.

Scripts:
- iniciar-servidor-tmarket.bat
- detener-servidor-tmarket.bat
- abrir-tmarket-tpv.bat
- habilitar-firewall-tmarket-admin.bat
- diagnostico-servidor-tmarket.bat

Base de datos:
$InstallDir\data\tmarket.sqlite

Respaldos:
- Antes de cada actualizacion el instalador crea un respaldo verificado.
- Respaldos preventivos: $InstallDir\backups\pre-update
- Respaldos diarios automaticos: $InstallDir\backups\daily
- Respaldos manuales: $InstallDir\backups
- Copia extra en escritorio: TMarketPOS-respaldos
- Manifiesto de instalacion: $InstallDir\install-manifest.json

Usuarios y PIN:
- En primer uso se debe configurar un PIN nuevo de administrador.
- Ya no se recomienda ni se permite trabajar con Administrador / 0000.
"@
Set-Content -LiteralPath (Join-Path $InstallDir "LEEME-SERVIDOR.txt") -Value $readme -Encoding UTF8

$desktop = [Environment]::GetFolderPath("Desktop")
$uninstallerContent = @"
`$ErrorActionPreference = "SilentlyContinue"
`$installDir = "$($InstallDir.Replace('"','`"'))"
`$port = $Port
`$startMenuDir = "$($startMenuDir.Replace('"','`"'))"
`$programsRoot = "$($programsRoot.Replace('"','`"'))"
`$desktop = "$($desktop.Replace('"','`"'))"
`$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\TMarketPOSServidor"

Get-NetTCPConnection -LocalPort `$port -State Listen -ErrorAction SilentlyContinue | ForEach-Object {
    Stop-Process -Id `$_.OwningProcess -Force -ErrorAction SilentlyContinue
}
schtasks /Delete /TN "TMarketPOSServer" /F | Out-Null
Remove-Item -LiteralPath (Join-Path `$desktop "T Market POS - Iniciar servidor.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$desktop "T Market POS.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$programsRoot "T Market POS Servidor.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$startMenuDir "T Market POS Servidor.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$startMenuDir "Iniciar servidor T Market POS.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$startMenuDir "Detener servidor T Market POS.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$startMenuDir "Diagnostico servidor T Market POS.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath (Join-Path `$startMenuDir "Desinstalar T Market POS Servidor.lnk") -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath `$regPath -Recurse -Force -ErrorAction SilentlyContinue
if(Test-Path -LiteralPath `$installDir) {
    `$backupDir = Join-Path ([Environment]::GetFolderPath("Desktop")) ("TMarketPOS-respaldo-servidor-" + (Get-Date -Format "yyyyMMdd-HHmmss"))
    New-Item -ItemType Directory -Force -Path `$backupDir | Out-Null

    `$itemsToBackup = @(
        "data",
        "backups",
        "install-manifest.json",
        "LEEME-SERVIDOR.txt",
        "README.md",
        "README-INSTALAR-SERVIDOR.txt"
    )

    foreach(`$item in `$itemsToBackup) {
        `$source = Join-Path `$installDir `$item
        if(Test-Path -LiteralPath `$source) {
            `$destination = Join-Path `$backupDir `$item
            Copy-Item -LiteralPath `$source -Destination `$destination -Recurse -Force -ErrorAction SilentlyContinue
        }
    }

    Remove-Item -LiteralPath `$installDir -Recurse -Force -ErrorAction SilentlyContinue
    Write-Host "Servidor desinstalado. Respaldo de data, backups y manifiesto creado en: `$backupDir"
} else {
    Write-Host "Servidor T Market POS desinstalado."
}
Start-Sleep -Seconds 3
"@
Set-Content -LiteralPath $uninstallerPath -Value $uninstallerContent -Encoding UTF8
New-Shortcut -Path (Join-Path $startMenuDir "Desinstalar T Market POS Servidor.lnk") -TargetPath $powerShellPath -Arguments "-NoProfile -ExecutionPolicy Bypass -File `"$uninstallerPath`"" -WorkingDirectory $InstallDir -IconLocation $appIcon

New-Item -Path $uninstallRegPath -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "DisplayName" -Value "T Market POS Servidor" -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "DisplayVersion" -Value "5.8.20" -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "Publisher" -Value "T Market" -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "InstallLocation" -Value $InstallDir -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "DisplayIcon" -Value $appIcon -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "UninstallString" -Value "`"$powerShellPath`" -NoProfile -ExecutionPolicy Bypass -File `"$uninstallerPath`"" -PropertyType String -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "NoModify" -Value 1 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $uninstallRegPath -Name "NoRepair" -Value 0 -PropertyType DWord -Force | Out-Null

Write-Step "Instalacion terminada sin tocar tus datos."
Write-Host ""
if($preUpdateBackup) { Write-Host "Respaldo preventivo verificado: $preUpdateBackup" -ForegroundColor Green }
Write-Host "Abre el TPV aqui: $url" -ForegroundColor Green
Write-Host "Desde otras PCs: $networkUrl" -ForegroundColor Green
if($recommendedNetworkUrls.Count) {
    Write-Host "Recomendacion: reserva en el modem una IP alta para este servidor." -ForegroundColor Yellow
    Write-Host "Nombre recomendado de la PC servidor: TMARKET-SERVIDOR" -ForegroundColor Yellow
    Write-Host "URL preferida para clientes: http://tmarket-servidor:$Port" -ForegroundColor Yellow
    Write-Host "Preferida: $($recommendedNetworkUrls[0])" -ForegroundColor Yellow
    if($recommendedNetworkUrls.Count -gt 1) {
        $alternatives = $recommendedNetworkUrls | Select-Object -Skip 1
        Write-Host "Alternativas: $($alternatives -join ', ')" -ForegroundColor Yellow
    }
}
Write-Host "Base de datos conservada en: $InstallDir\data\tmarket.sqlite" -ForegroundColor Green
Write-Host "Diagnostico/Reparacion: Menu Inicio > T Market POS > Diagnostico o Reparar instalacion" -ForegroundColor Cyan





