# ==============================================================================
#  lf_mri_platform -- Start services and GUI
#  Usage:
#    .\start.ps1                   -- plug mode: Docker + GUI
#    .\start.ps1 -Mode real        -- real mode: Docker + spectrometer + GUI
#    .\start.ps1 -GuiOnly          -- GUI only (services already running)
#    .\start.ps1 -ServicesOnly     -- Docker + spectrometer, no GUI
#    .\start.ps1 -SkipInstall      -- skip venv check
#    .\start.ps1 -SkipSpectrometer -- skip native spectrometer even in real mode
# ==============================================================================
param(
    [ValidateSet("plug", "real")]
    [string]$Mode = "plug",
    [switch]$GuiOnly,
    [switch]$ServicesOnly,
    [switch]$SkipInstall,
    [switch]$SkipSpectrometer
)

$ErrorActionPreference = "Stop"
$Root       = $PSScriptRoot
$GuiDir     = Join-Path $Root "apps\gui"
$VenvPython = Join-Path $GuiDir ".venv\Scripts\python.exe"
$AppScript  = Join-Path $Root  "apps\gui\app.py"
$EnvFile    = Join-Path $Root  ".env"
$EnvExample = Join-Path $Root  ".env.example"
$LogFile    = Join-Path $Root  "start_log.txt"

# Write all output to log file so errors are readable even if window closes
Start-Transcript -Path $LogFile -Append | Out-Null
Write-Host "=== start.ps1  $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')  Mode=$Mode ==="

function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
function Write-OK($msg)   { Write-Host "    [OK] $msg" -ForegroundColor Green }
function Write-Warn($msg) { Write-Host "    [!!] $msg" -ForegroundColor Yellow }
function Write-Fail($msg) {
    Write-Host "`n[FAIL] $msg" -ForegroundColor Red
    Write-Host "  Log: $LogFile" -ForegroundColor DarkGray
    try { Stop-Transcript | Out-Null } catch {}
    Write-Host "`n  Press Enter to close..." -ForegroundColor DarkGray
    $null = Read-Host
    exit 1
}

# Keep window open on any unhandled error
trap {
    Write-Host "`n[ERROR] $_" -ForegroundColor Red
    Write-Host "  Log: $LogFile" -ForegroundColor DarkGray
    try { Stop-Transcript | Out-Null } catch {}
    Write-Host "`n  Press Enter to close..." -ForegroundColor DarkGray
    $null = Read-Host
    exit 1
}

function Get-EnvPort($key, $default) {
    if (Test-Path $EnvFile) {
        $line = Get-Content $EnvFile | Select-String "^$key=(\d+)"
        if ($line) { return $line.Matches[0].Groups[1].Value }
    }
    return $default
}

# Returns $true if the TCP port is accepting connections.
# Using TCP instead of HTTP so HTTP 4xx/auth errors don't cause false negatives.
function Test-ServiceUp($svc) {
    try {
        $uri  = [System.Uri]$svc.Url
        $port = if ($uri.Port -gt 0) { $uri.Port } else { 80 }
        $tcp  = New-Object System.Net.Sockets.TcpClient
        $ar   = $tcp.BeginConnect($uri.Host, $port, $null, $null)
        $ok   = $ar.AsyncWaitHandle.WaitOne(1500, $false)
        try { $tcp.Close() } catch {}
        return $ok
    } catch {
        return $false
    }
}

# -- 1. Check / install GUI venv -----------------------------------------------
if (-not $GuiOnly -and -not $SkipInstall) {
    Write-Step "Checking GUI environment"
    $needsInstall = $false
    if (-not (Test-Path $VenvPython)) {
        Write-Warn "Virtual environment not found -- running install"
        $needsInstall = $true
    } else {
        & $VenvPython -c "import PySide6" 2>$null
        if ($LASTEXITCODE -ne 0) {
            Write-Warn "GUI packages missing -- running install"
            $needsInstall = $true
        } else {
            Write-OK "Virtual environment ready"
        }
    }
    if ($needsInstall) {
        $installScript = Join-Path $Root "install.ps1"
        if (-not (Test-Path $installScript)) { Write-Fail "install.ps1 not found" }
        & powershell.exe -ExecutionPolicy Bypass -File $installScript
        if ($LASTEXITCODE -ne 0) { Write-Fail "install.ps1 failed" }
    }
}

# -- 2. Docker -----------------------------------------------------------------
if (-not $GuiOnly) {
    Write-Step "Checking Docker"
    $dockerOk = $false
    try { & docker info *>$null; $dockerOk = $true } catch {}

    if (-not $dockerOk) {
        Write-Warn "Docker not responding -- starting Docker Desktop..."
        $dockerExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
        if (Test-Path $dockerExe) { Start-Process $dockerExe }
        else { Write-Fail "Docker Desktop not found -- install from https://docker.com" }

        $waited = 0
        while ($waited -lt 60) {
            Start-Sleep 5
            $waited += 5
            try { & docker info *>$null; $dockerOk = $true; break } catch {}
            Write-Host "    ... $waited/60 s" -ForegroundColor DarkGray
        }
        if (-not $dockerOk) { Write-Fail "Docker did not start in time" }
    }
    Write-OK "Docker is running"

    # -- 3. .env ---------------------------------------------------------------
    if (-not (Test-Path $EnvFile)) {
        if (Test-Path $EnvExample) {
            Copy-Item $EnvExample $EnvFile
            Write-Warn ".env created from .env.example"
        } else {
            Write-Warn ".env missing -- using Compose defaults"
        }
    }

    # -- 4. Start containers ---------------------------------------------------
    Write-Step "Starting Docker services (mode: $Mode)"
    $env:ORCHESTRATOR_MODE = $Mode
    $envArgs = if (Test-Path $EnvFile) { @("--env-file", $EnvFile) } else { @() }
    & docker compose @envArgs up -d
    if ($LASTEXITCODE -ne 0) { Write-Fail "docker compose up failed" }
    Write-OK "Containers started"

    # -- 5. Native spectrometer ------------------------------------------------
    if (-not $SkipSpectrometer) {
        Write-Step "Starting native spectrometer"
        $SpecDir  = Join-Path $Root "services\spectrometer"
        $SpecVenv = Join-Path $SpecDir "mvenv\Scripts\python.exe"
        $PicoExe  = Join-Path $SpecDir "bin\pico-tcp.exe"

        $oldPico = Get-Process -Name "pico-tcp" -ErrorAction SilentlyContinue
        if ($oldPico) { $oldPico | Stop-Process -Force; Write-Warn "Killed stale pico-tcp.exe" }

        if (-not (Test-Path $SpecVenv)) {
            Write-Warn "Spectrometer venv not found -- creating..."
            Push-Location $SpecDir
            & python -m venv mvenv
            if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to create spectrometer venv" }
            Pop-Location
            Write-OK "Spectrometer venv created"
        } else {
            Write-OK "Spectrometer venv found"
        }

        # Always sync packages (catches new deps after git pull)
        Write-Host "    Installing/updating spectrometer packages..." -ForegroundColor DarkGray
        Push-Location $SpecDir
        & $SpecVenv -m pip install -q --upgrade pip
        $pipOut = & $SpecVenv -m pip install -q -r requirements.txt 2>&1
        $pipFailed = ($LASTEXITCODE -ne 0)
        Pop-Location
        if ($pipFailed) {
            Write-Warn "pip install had issues:`n$pipOut"
        } else {
            Write-OK "Packages up to date"
        }

        # Migrations -- show output so errors are visible
        Write-Host "    Running migrations..." -ForegroundColor DarkGray
        Push-Location $SpecDir
        $migrateOut = & $SpecVenv manage.py migrate --noinput 2>&1
        $migrateFailed = ($LASTEXITCODE -ne 0)
        Pop-Location
        if ($migrateFailed) {
            Write-Warn "Migration warnings:`n$migrateOut"
        } else {
            Write-OK "Migrations applied"
        }

        # pico-tcp.exe
        if (Test-Path $PicoExe) {
            Start-Process $PicoExe -WindowStyle Normal
            Write-OK "pico-tcp.exe started (visible window)"
        } else {
            Write-Warn "bin\pico-tcp.exe not found -- ADC proxy not started"
        }

        # Django runserver -- run via "cmd /k" so window stays open on error
        $specPort = Get-EnvPort "SPECTROMETER_PORT" "1818"
        $alreadyUp = $false
        try {
            $r = Invoke-WebRequest "http://localhost:$specPort/api/" `
                     -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
            $alreadyUp = ($r.StatusCode -lt 400)
        } catch {}

        if ($alreadyUp) {
            Write-OK "Spectrometer already responding on port $specPort -- checking devices..."
            # Fall through to device registration check below
        } else {
            # Title makes the window easy to find in the taskbar.
            # /k keeps the window open even if Django crashes at startup.
            # --noreload disables Django's file-watcher subprocess (cleaner in cmd).
            $runCmd = "`"$SpecVenv`" manage.py runserver 0.0.0.0:$specPort --noreload"
            Start-Process "cmd.exe" `
                -ArgumentList "/k title LF-MRI Spectrometer && $runCmd" `
                -WorkingDirectory $SpecDir `
                -WindowStyle Normal
            Write-OK "Spectrometer started (window: 'LF-MRI Spectrometer', port $specPort)"
        }

        # -- Auto-register hardware devices if DB is empty --------------------
        Write-Host "    Waiting for spectrometer API..." -ForegroundColor DarkGray
        $apiReady = $false
        $apiWait  = 0
        while ($apiWait -lt 20) {
            if (Test-ServiceUp @{ Url = "http://localhost:$specPort/api/" }) {
                $apiReady = $true; break
            }
            Start-Sleep 1; $apiWait++
        }

        if ($apiReady) {
            try {
                $devResp = Invoke-WebRequest "http://localhost:$specPort/api/devices/" `
                               -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
                $devJson  = $devResp.Content | ConvertFrom-Json
                $devCount = if ($devJson.PSObject.Properties['results']) {
                    $devJson.results.Count
                } else {
                    @($devJson).Count
                }

                if ($devCount -eq 0) {
                    Write-Warn "No devices in DB -- registering hardware..."
                    $base = "http://localhost:$specPort/api/devices/"
                    $hdr  = @{ "Content-Type" = "application/json" }
                    $devs = @(
                        '{"device_type":"ADC",  "brend":"Picoscope", "serial_model":"PS4000A", "proto":"adc_default",  "proto_interface":"TCP"}',
                        '{"device_type":"SDR",  "brend":"HackRF",   "serial_model":"HackRF",  "proto":"sdr_default",  "proto_interface":"USB"}',
                        '{"device_type":"SYNC", "brend":"Arduino",  "serial_model":"DuePP",   "proto":"sync_default", "proto_interface":"USB"}',
                        '{"device_type":"GRA",  "brend":"ITMO",     "serial_model":"GRU",     "proto":"gra_default",  "proto_interface":"UDP"}'
                    )
                    foreach ($body in $devs) {
                        try {
                            Invoke-WebRequest $base -Method Post -Headers $hdr `
                                -Body $body -UseBasicParsing -TimeoutSec 5 | Out-Null
                            $name = ($body | Select-String '"serial_model":"([^"]+)"').Matches[0].Groups[1].Value
                            Write-Host "    Registered: $name" -ForegroundColor DarkGray
                        } catch {
                            Write-Warn "  Failed to register device: $_"
                        }
                    }
                    Write-OK "Hardware devices registered"
                } else {
                    Write-OK "Devices already in DB ($devCount found)"
                }
            } catch {
                Write-Warn "Could not check/register devices: $_"
            }
        } else {
            Write-Warn "Spectrometer API not ready in 20 s -- skipping device registration"
        }
    }

    # -- 6. Health check -------------------------------------------------------
    Write-Step "Waiting for services to become healthy"
    $checkSpec = -not $SkipSpectrometer
    $services = @(
        @{ Name = "Orchestrator";  Url = "http://localhost:$(Get-EnvPort 'ORCHESTRATOR_PORT'  '1717')/health"; Required = $true      },
        @{ Name = "Seq-Interp";    Url = "http://localhost:$(Get-EnvPort 'SEQ_INTERP_PORT'    '7475')/health"; Required = $true      },
        @{ Name = "Reconstructor"; Url = "http://localhost:$(Get-EnvPort 'RECONSTRUCTOR_PORT' '8081')/health"; Required = $true      },
        @{ Name = "Spectroscopy";  Url = "http://localhost:$(Get-EnvPort 'SPECTROSCOPY_PORT'  '8002')/health"; Required = $true      },
        @{ Name = "Spectrometer";  Url = "http://localhost:$(Get-EnvPort 'SPECTROMETER_PORT'  '1818')/api/";  Required = $checkSpec; AllowedCodes = @(200,301,302,401,403) }
    )

    $maxWait = 120; $interval = 3; $elapsed = 0

    while ($elapsed -lt $maxWait) {
        $pending = @()
        foreach ($svc in $services) {
            if (-not $svc.Required) { continue }
            if (-not (Test-ServiceUp $svc)) { $pending += $svc.Name }
        }
        if ($pending.Count -eq 0) { Write-OK "All required services healthy"; break }
        Write-Host ("    ... {0}/{1} s  waiting: {2}" -f $elapsed, $maxWait, ($pending -join ", ")) -ForegroundColor DarkGray
        Start-Sleep $interval
        $elapsed += $interval
    }
    if ($elapsed -ge $maxWait) { Write-Warn "Some services did not respond in time -- continuing anyway" }

    Write-Host ""
    foreach ($svc in $services) {
        $ok = Test-ServiceUp $svc
        $icon  = if ($ok) { "[OK]" } else { "[--]" }
        $color = if ($ok) { "Green" } elseif ($svc.Required) { "Yellow" } else { "DarkGray" }
        $suffix = if (-not $svc.Required -and -not $ok) { "  (native -- start manually)" } else { "" }
        Write-Host ("    {0,-6} {1,-16} {2}{3}" -f $icon, $svc.Name, $svc.Url, $suffix) -ForegroundColor $color
    }

    # -- 7. Write server_config.json -------------------------------------------
    Write-Step "Configuring GUI"
    $orchPort  = Get-EnvPort "ORCHESTRATOR_PORT" "1717"
    $seqPort   = Get-EnvPort "SEQ_INTERP_PORT"   "7475"
    $spectPort = Get-EnvPort "SPECTROSCOPY_PORT" "8002"
    $cfgPath   = Join-Path $Root "apps\gui\cfg\server_config.json"
    $cfgObj = @{
        srv_name         = "srv_interp"
        log_dir          = "log"
        upload_dir       = "data/input"
        output_dir       = "data/output"
        server_host      = "0.0.0.0"
        server_port      = [int]$seqPort
        orchestrator_url = "http://localhost:$orchPort"
        seq_interp_url   = "http://localhost:$seqPort"
        spectroscopy_url = "http://localhost:$spectPort"
        mode             = $Mode
    }
    $cfgObj | ConvertTo-Json -Depth 3 | Set-Content $cfgPath -Encoding utf8
    Write-OK "server_config.json updated (mode=$Mode  orch=:$orchPort  seq=:$seqPort  spectro=:$spectPort)"
}

# -- 8. Launch GUI -------------------------------------------------------------
if (-not $ServicesOnly) {
    Write-Step "Launching GUI"
    if (-not (Test-Path $VenvPython)) {
        Write-Warn "Venv not found -- falling back to system Python"
        $VenvPython = "python"
    }
    if (-not (Test-Path $AppScript)) { Write-Fail "GUI entry point not found: $AppScript" }
    Start-Process $VenvPython -ArgumentList "`"$AppScript`"" -WorkingDirectory $Root -WindowStyle Normal
    Write-OK "GUI launched"
}

# -- Summary -------------------------------------------------------------------
Write-Host ""
Write-Host "============================================================" -ForegroundColor Green
Write-Host ("  LF-MRI platform is running  [{0} mode]" -f $Mode.ToUpper()) -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green
Write-Host ""
if (-not $SkipSpectrometer) {
    Write-Host "  Stop spectrometer:  close the spectrometer terminal window"
    Write-Host "  Stop pico-tcp:      services\spectrometer\autokill.bat"
}
Write-Host "  Stop all:           .\stop.ps1"
Write-Host "  Update code:        .\update.bat"
Write-Host ""
Write-Host "  Log saved to: $LogFile" -ForegroundColor DarkGray
try { Stop-Transcript | Out-Null } catch {}
Write-Host "  Press Enter to close this window..." -ForegroundColor DarkGray
$null = Read-Host
