$ErrorActionPreference = "Stop"

$baseUrl = if ($env:RAIL_WEB_BASE_URL) { $env:RAIL_WEB_BASE_URL.TrimEnd("/") } else { "http://localhost:8088" }
$apiUrl = "$baseUrl/api/v1"
$runKey = (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmssfff")
$tempPng = Join-Path ([IO.Path]::GetTempPath()) "rail-capability-$runKey.png"

function Invoke-RailApi {
    param(
        [Parameter(Mandatory = $true)][string]$Method,
        [Parameter(Mandatory = $true)][string]$Path,
        [object]$Body
    )
    $parameters = @{
        Method = $Method
        Uri = "$apiUrl$Path"
        TimeoutSec = 60
    }
    if ($null -ne $Body) {
        $json = $Body | ConvertTo-Json -Depth 20 -Compress
        $parameters.ContentType = "application/json; charset=utf-8"
        $parameters.Body = [Text.Encoding]::UTF8.GetBytes($json)
    }
    try {
        $response = Invoke-RestMethod @parameters
    }
    catch {
        $details = $_.Exception.Message
        if ($_.Exception.Response) {
            try {
                $reader = New-Object IO.StreamReader($_.Exception.Response.GetResponseStream())
                $responseBody = $reader.ReadToEnd()
                if ($responseBody) { $details = "$details Response: $responseBody" }
            }
            catch { }
        }
        throw "$Method $Path failed. $details"
    }
    if ($response.code -ne "0") {
        throw "$Method $Path returned application code $($response.code)"
    }
    return $response.data
}

function Wait-DemoRun {
    param([string]$RunId)
    for ($i = 0; $i -lt 120; $i++) {
        Start-Sleep -Milliseconds 500
        $snapshot = Invoke-RailApi -Method GET -Path "/demo/runs/$RunId"
        if ($snapshot.status -in @("completed", "failed", "cancelled")) {
            return $snapshot
        }
    }
    throw "Full-chain run $RunId did not reach a terminal state"
}

function Invoke-MissionCommand {
    param([string]$MissionId, [string]$Command, [int]$Sequence)
    return Invoke-RailApi -Method POST -Path "/uav/missions/$MissionId/commands" -Body @{
        command = $Command
        reason = "capability completion regression"
        idempotency_key = "$runKey-mission-$Sequence"
        requested_by = "system-tester"
        parameters = @{}
    }
}

function Invoke-WorkOrderAction {
    param([string]$WorkOrderId, [string]$Action, [int]$Sequence)
    return Invoke-RailApi -Method POST -Path "/workorders/$WorkOrderId/actions" -Body @{
        action = $Action
        operator_id = "user-field"
        operator_name = "System Tester"
        result = "PASS"
        comment = "capability completion regression step $Sequence"
        metadata = @{ test_run = $runKey }
    }
}

try {
    Write-Host "Checking capability registry and spatial objects..."
    $completion = Invoke-RailApi -Method GET -Path "/capabilities/completion"
    if (@($completion.capabilities).Count -lt 14) { throw "Capability registry is incomplete" }

    $objects = Invoke-RailApi -Method GET -Path "/inspection/objects"
    $object = @($objects.objects | Select-Object -First 1)[0]
    if (-not $object.object_id) { throw "No inspection object is available" }

    Write-Host "Creating and publishing a route..."
    $route = Invoke-RailApi -Method POST -Path "/routes" -Body @{
        name = "Capability regression route $runKey"
        object_id = $object.object_id
        waypoints = @(
            @{ longitude = 116.100; latitude = 39.100; altitude_m = 80; speed_mps = 6; gimbal_pitch_deg = -45; actions = @("PHOTO") },
            @{ longitude = 116.105; latitude = 39.103; altitude_m = 82; speed_mps = 6; gimbal_pitch_deg = -60; actions = @("PHOTO", "THERMAL") },
            @{ longitude = 116.110; latitude = 39.105; altitude_m = 80; speed_mps = 5; gimbal_pitch_deg = -45; actions = @("PHOTO") }
        )
        flight_parameters = @{ speed_mps = 6; return_altitude_m = 100; lost_link_action = "RETURN_HOME" }
        payload_actions = @(@{ type = "PHOTO"; interval_seconds = 2 })
        created_by = "system-tester"
    }
    $validation = Invoke-RailApi -Method POST -Path "/route-versions/$($route.route_version_id)/validate"
    if (-not $validation.valid) { throw "Route validation failed" }
    $published = Invoke-RailApi -Method POST -Path "/route-versions/$($route.route_version_id)/publish?approvedBy=system-tester"
    if ($published.status -ne "PUBLISHED") { throw "Route publication failed" }

    Write-Host "Creating a plan and generating an inspection task..."
    $plan = Invoke-RailApi -Method POST -Path "/inspection/plans" -Body @{
        name = "Capability regression plan $runKey"
        plan_type = "SPECIAL"
        schedule_rule = "MANUAL"
        object_ids = @($object.object_id)
        scene_set = @("FOREIGN_OBJECT", "FENCE_DAMAGE")
        priority = "high"
        owner_org_id = "org-railway"
        created_by = "system-tester"
    }
    $activated = Invoke-RailApi -Method POST -Path "/inspection/plans/$($plan.plan_id)/activate"
    if ($activated.status -ne "ACTIVE") { throw "Plan activation failed" }
    $generated = Invoke-RailApi -Method POST -Path "/inspection/plans/$($plan.plan_id)/generate-tasks"
    $taskId = @($generated.task_ids)[0]
    if (-not $taskId) { throw "Plan did not generate an inspection task" }

    Write-Host "Checking simulator connection and executing a flight mission..."
    $connections = Invoke-RailApi -Method GET -Path "/uav/connections"
    $connection = @($connections.connections | Where-Object { $_.vendor_code -eq "SIMULATOR" } | Select-Object -First 1)[0]
    if (-not $connection.connection_id) { throw "Simulator connection is unavailable" }
    $health = Invoke-RailApi -Method POST -Path "/uav/connections/$($connection.connection_id)/check"
    if ($health.status -ne "AVAILABLE") { throw "Simulator connection check failed" }
    Invoke-RailApi -Method POST -Path "/uav/connections/$($connection.connection_id)/sync-devices" | Out-Null
    $devices = Invoke-RailApi -Method GET -Path "/uav/devices"
    $device = @($devices.devices | Where-Object { $_.connection_id -eq $connection.connection_id -and $_.status -eq "ONLINE" } | Select-Object -First 1)[0]
    if (-not $device.device_id) { throw "No online simulator device is available" }

    $mission = Invoke-RailApi -Method POST -Path "/uav/missions" -Body @{
        task_id = $taskId
        route_id = $route.route_id
        connection_id = $connection.connection_id
        device_id = $device.device_id
        idempotency_key = "$runKey-dispatch"
        requested_by = "system-tester"
    }
    $missionState = Invoke-MissionCommand -MissionId $mission.mission_id -Command "MISSION_START" -Sequence 0
    for ($i = 1; $i -le 4; $i++) {
        $missionState = Invoke-MissionCommand -MissionId $mission.mission_id -Command "SIMULATE_PROGRESS" -Sequence $i
    }
    if ($missionState.status -ne "COMPLETED" -or [int]$missionState.progress -ne 100) { throw "Mission did not complete" }
    $telemetry = Invoke-RailApi -Method GET -Path "/uav/missions/$($mission.mission_id)/telemetry"
    if (@($telemetry.telemetry).Count -lt 5) { throw "Mission telemetry is incomplete" }

    Write-Host "Uploading a resource and running preprocessing..."
    $pngBytes = [Convert]::FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9Zl1sAAAAASUVORK5CYII=")
    [IO.File]::WriteAllBytes($tempPng, $pngBytes)
    $upload = Invoke-RailApi -Method POST -Path "/uploads" -Body @{
        task_id = $taskId
        mission_id = $mission.mission_id
        resource_type = "VISIBLE_IMAGE"
        file_name = "capability-$runKey.png"
        content_type = "image/png"
        total_size_bytes = $pngBytes.Length
        part_size_bytes = 1048576
        created_by = "system-tester"
    }
    $partRaw = & curl.exe -sS -X PUT -F "chunk=@$tempPng;type=image/png" "$apiUrl/uploads/$($upload.upload_session_id)/parts/0"
    if ($LASTEXITCODE -ne 0) { throw "Resource part upload failed with curl exit code $LASTEXITCODE" }
    $part = $partRaw | ConvertFrom-Json
    if ($part.code -ne "0" -or $part.data.status -ne "UPLOADED") { throw "Resource part upload was not accepted" }
    $completedUpload = Invoke-RailApi -Method POST -Path "/uploads/$($upload.upload_session_id)/complete"
    $preprocess = Invoke-RailApi -Method POST -Path "/preprocess/jobs/$($completedUpload.preprocess_job_id)/run"
    if ($preprocess.status -ne "COMPLETED" -or $preprocess.quality_status -ne "PASSED") { throw "Preprocessing did not pass" }

    Write-Host "Creating a fresh closed-loop work order and executing field actions..."
    $demoRun = Invoke-RailApi -Method POST -Path "/demo/runs" -Body @{}
    $demoSnapshot = Wait-DemoRun -RunId $demoRun.run_id
    if ($demoSnapshot.status -ne "completed") { throw "Full-chain source run failed" }
    $workOrderId = @($demoSnapshot.entity_index.workorder_ids)[0]
    if (-not $workOrderId) { throw "Full-chain run did not generate a work order" }

    $workOrderActions = @("ACCEPT", "DEPART", "ARRIVE", "START_PROCESSING")
    for ($i = 0; $i -lt $workOrderActions.Count; $i++) {
        Invoke-WorkOrderAction -WorkOrderId $workOrderId -Action $workOrderActions[$i] -Sequence $i | Out-Null
    }
    $evidenceRaw = & curl.exe -sS -X POST -F "file=@$tempPng;type=image/png" "$apiUrl/workorders/$workOrderId/attachments?phase=DURING&uploadedBy=system-tester"
    if ($LASTEXITCODE -ne 0) { throw "Evidence upload failed with curl exit code $LASTEXITCODE" }
    $evidence = $evidenceRaw | ConvertFrom-Json
    if ($evidence.code -ne "0" -or -not $evidence.data.attachment_id) { throw "Evidence upload was not accepted" }
    $reviewActions = @("SUBMIT", "START_REVIEW", "APPROVE")
    for ($i = 0; $i -lt $reviewActions.Count; $i++) {
        $finalWorkOrder = Invoke-WorkOrderAction -WorkOrderId $workOrderId -Action $reviewActions[$i] -Sequence ($i + 4)
    }
    if ($finalWorkOrder.status -ne "closed") { throw "Work order did not close" }
    $actions = Invoke-RailApi -Method GET -Path "/workorders/$workOrderId/actions"
    if (@($actions.actions).Count -lt 8) { throw "Work order action audit trail is incomplete" }

    $reinspection = Invoke-RailApi -Method POST -Path "/workorders/$workOrderId/reinspection" -Body @{
        requested_by = "system-tester"
        route_id = $route.route_id
        required_methods = @("UAV_REFLIGHT", "AI_COMPARE")
    }
    if ($reinspection.status -ne "TASK_CREATED") { throw "Reinspection task creation failed" }
    $archive = Invoke-RailApi -Method POST -Path "/archive-packages" -Body @{
        business_type = "WORKORDER"
        business_id = $workOrderId
        created_by = "system-tester"
    }
    if ($archive.status -ne "READY") { throw "Archive package was not created" }

    Write-Host "Checking GIS and notification views..."
    $gis = Invoke-RailApi -Method GET -Path "/gis/features"
    if (@($gis.features).Count -lt 1) { throw "GIS feature collection is empty" }
    $notifications = Invoke-RailApi -Method GET -Path "/notifications"
    $notification = @($notifications.notifications | Where-Object { $_.status -ne "READ" } | Select-Object -First 1)[0]
    if ($notification.notification_id) {
        $read = Invoke-RailApi -Method POST -Path "/notifications/$($notification.notification_id)/read"
        if ($read.status -ne "READ") { throw "Notification read receipt failed" }
    }

    [ordered]@{
        capability_count = @($completion.capabilities).Count
        object_id = $object.object_id
        route_id = $route.route_id
        plan_id = $plan.plan_id
        task_id = $taskId
        mission_id = $mission.mission_id
        mission_status = $missionState.status
        telemetry_count = @($telemetry.telemetry).Count
        resource_id = $completedUpload.resource_id
        preprocess_status = $preprocess.quality_status
        workorder_id = $workOrderId
        workorder_status = $finalWorkOrder.status
        evidence_id = $evidence.data.attachment_id
        reinspection_task_id = $reinspection.reinspection_task_id
        archive_no = $archive.archive_no
        gis_feature_count = @($gis.features).Count
        notification_read_checked = [bool]$notification.notification_id
    } | ConvertTo-Json -Depth 8
    Write-Host "Capability completion test passed."
}
finally {
    if (Test-Path -LiteralPath $tempPng) {
        Remove-Item -LiteralPath $tempPng -Force
    }
}
