param([switch]$Elevated)

$ErrorActionPreference = "Stop"
$ProductName = "CNC Accuracy Retention Evaluation System"
$InstallDir = Join-Path $env:ProgramData "CNCIndustrialEvaluation\PrecisionRetention"
$DefaultPort = 3100
$PortVariable = "PRECISION_HTTP_PORT"
$FirewallRule = "CNC Accuracy Retention Evaluation System LAN"

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

function New-HexSecret([int]$ByteCount) {
    $bytes = New-Object byte[] $ByteCount
    $generator = [Security.Cryptography.RandomNumberGenerator]::Create()
    try {
        $generator.GetBytes($bytes)
    } finally {
        $generator.Dispose()
    }
    return (($bytes | ForEach-Object { $_.ToString("x2") }) -join "")
}

function Add-DockerPath {
    $dockerBin = Join-Path $env:ProgramFiles "Docker\Docker\resources\bin"
    if ((Test-Path $dockerBin) -and ($env:Path -notlike "*$dockerBin*")) {
        $env:Path = "$env:Path;$dockerBin"
    }
}

function Ensure-Docker {
    Add-DockerPath
    if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
        if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
            Start-Process "https://www.docker.com/products/docker-desktop/"
            throw "Docker Desktop is required. Install it from the opened official page, restart Windows, then run this installer again."
        }

        Write-Host "Installing Docker Desktop. This can take several minutes..." -ForegroundColor Cyan
        winget install --id Docker.DockerDesktop --exact --accept-package-agreements --accept-source-agreements
        if ($LASTEXITCODE -ne 0) {
            throw "Docker Desktop installation failed. Check the network connection and run this installer again."
        }
        Add-DockerPath
    }

    if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
        throw "Docker Desktop was installed but Windows must be restarted. Restart Windows, then run this installer again."
    }

    docker info *> $null
    if ($LASTEXITCODE -eq 0) {
        return
    }

    $desktopExe = Join-Path $env:ProgramFiles "Docker\Docker\Docker Desktop.exe"
    if (Test-Path $desktopExe) {
        Write-Host "Starting Docker Desktop..." -ForegroundColor Cyan
        Start-Process $desktopExe
    }

    for ($attempt = 0; $attempt -lt 90; $attempt++) {
        Start-Sleep -Seconds 2
        docker info *> $null
        if ($LASTEXITCODE -eq 0) {
            return
        }
    }

    throw "Docker Desktop is not ready. Complete any Docker window prompts or restart Windows, then run this installer again."
}

function Install-ProgramFiles {
    $packageRoot = $PSScriptRoot
    if (Test-Path (Join-Path $packageRoot "docker-compose.yml")) {
        $programSource = Get-Item $packageRoot
    } else {
        $programSource = Get-ChildItem -Path $packageRoot -Directory | Where-Object {
            Test-Path (Join-Path $_.FullName "docker-compose.yml")
        } | Select-Object -First 1
    }

    if (-not $programSource) {
        throw "The program directory is missing or the package was not extracted completely."
    }

    New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
    robocopy $programSource.FullName $InstallDir /E /R:2 /W:2 /XD .git node_modules venv __pycache__ /XF .env | Out-Null
    if ($LASTEXITCODE -ge 8) {
        throw "Unable to copy program files to $InstallDir."
    }
}

function Initialize-Environment {
    $envFile = Join-Path $InstallDir ".env"
    $templateFile = Join-Path $InstallDir ".env.production.example"
    if (Test-Path $envFile) {
        return $false
    }
    if (-not (Test-Path $templateFile)) {
        throw "The environment template is missing."
    }

    $databasePassword = New-HexSecret 24
    $secretKey = New-HexSecret 48
    $adminPassword = "Admin-" + (New-HexSecret 8)
    $content = Get-Content $templateFile -Raw
    $content = $content.Replace("replace-with-a-long-random-database-password", $databasePassword)
    $content = $content.Replace("replace-with-python-secrets-token-urlsafe-48-output", $secretKey)
    $content = $content.Replace("replace-with-a-strong-initial-admin-password", $adminPassword)
    [IO.File]::WriteAllText($envFile, $content, (New-Object Text.UTF8Encoding($false)))

    $loginFile = Join-Path $InstallDir "FIRST_LOGIN.txt"
    $loginText = @(
        $ProductName
        ""
        "Address: http://localhost:$DefaultPort"
        "Username: admin"
        "Initial password: $adminPassword"
        ""
        "Change the initial password immediately after the first login."
    ) -join [Environment]::NewLine
    [IO.File]::WriteAllText($loginFile, $loginText, (New-Object Text.UTF8Encoding($false)))
    return $true
}

function Get-ConfiguredPort {
    $port = $DefaultPort
    $content = Get-Content (Join-Path $InstallDir ".env") -Raw
    if ($content -match "(?m)^$PortVariable=(\d+)\s*$") {
        $port = [int]$Matches[1]
    }
    return $port
}

function Configure-LanFirewall([int]$Port) {
    Get-NetFirewallRule -DisplayName $FirewallRule -ErrorAction SilentlyContinue | Remove-NetFirewallRule
    New-NetFirewallRule -DisplayName $FirewallRule -Direction Inbound -Action Allow -Protocol TCP -LocalPort $Port -Profile Domain,Private -RemoteAddress LocalSubnet | Out-Null
}

function Create-Shortcuts([int]$Port) {
    $desktop = [Environment]::GetFolderPath("CommonDesktopDirectory")
    if (-not $desktop) {
        $desktop = [Environment]::GetFolderPath("DesktopDirectory")
    }

    $shell = New-Object -ComObject WScript.Shell
    $startShortcut = $shell.CreateShortcut((Join-Path $desktop "Start Accuracy Retention Evaluation System.lnk"))
    $startShortcut.TargetPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
    $startShortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$(Join-Path $InstallDir 'start_windows.ps1')`""
    $startShortcut.WorkingDirectory = $InstallDir
    $startShortcut.IconLocation = "$env:SystemRoot\System32\SHELL32.dll,220"
    $startShortcut.Save()

    $urlFile = Join-Path $desktop "Open Accuracy Retention Evaluation System.url"
    [IO.File]::WriteAllText($urlFile, "[InternetShortcut]`r`nURL=http://localhost:$Port`r`n", (New-Object Text.UTF8Encoding($false)))
}

if (-not (Test-Administrator)) {
    $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Elevated"
    Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
    exit 0
}

Write-Host "============================================================"
Write-Host " $ProductName - Installer"
Write-Host "============================================================"

Install-ProgramFiles
$createdLogin = Initialize-Environment
Ensure-Docker
Set-Location $InstallDir

Write-Host "Building and starting the system. The first run downloads required runtime images..." -ForegroundColor Cyan
docker compose up -d --build
if ($LASTEXITCODE -ne 0) {
    throw "The software could not start. Check the Internet connection and Docker Desktop, then run the installer again."
}

$port = Get-ConfiguredPort
Configure-LanFirewall -Port $port
Create-Shortcuts -Port $port

$addresses = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
    Where-Object { $_.IPAddress -notmatch "^(127\.|169\.254\.)" } |
    Select-Object -ExpandProperty IPAddress -Unique

Write-Host ""
Write-Host "Installation completed." -ForegroundColor Green
Write-Host "Local address: http://localhost:$port"
foreach ($address in $addresses) {
    Write-Host "LAN address:   http://${address}:$port"
}
Write-Host "Desktop shortcuts have been created."

if ($createdLogin) {
    Start-Process notepad.exe (Join-Path $InstallDir "FIRST_LOGIN.txt")
}
Start-Process "http://localhost:$port"
