[CmdletBinding()]
param(
    [string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
    [string]$AnalyticsHost = '',
    [string]$AnalyticsUser = 'igor',
    [string]$RemoteRoot = '/opt/activitywatch/clickhouse-1c/landing',
    [string]$RemoteKeyPath = 'C:\ProgramData\AWatch-rus\ssh\awops_ed25519',
    [string]$RegistryWorkbookPath = ''
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$LogDir = 'C:\ProgramData\AWatch-rus\logs'
$LogPath = Join-Path $LogDir 'file1c-telemetry.log'
$ScpExe = Join-Path $env:WINDIR 'System32\OpenSSH\scp.exe'
$ExporterStatePath = Join-Path (Split-Path -Parent $ConfigPath) 'file1c-telemetry-state.json'

New-Item -ItemType Directory -Path $LogDir -Force | Out-Null

function Write-RunLog {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message
    )

    $line = '{0} {1}' -f ([DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')), $Message
    Add-Content -LiteralPath $LogPath -Value $line -Encoding UTF8
}

function Save-DeploymentConfig {
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Config,
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    $json = $Config | ConvertTo-Json -Depth 12
    Set-Content -LiteralPath $Path -Value $json -Encoding UTF8
}

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

    if (-not (Test-Path -LiteralPath $Path)) {
        return ''
    }

    $matchedHost = ''
    foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) {
        if ([string]$line -match 'upload complete analyticsHost=([^\s]+)') {
            $matchedHost = [string]$Matches[1]
        }
    }

    return $matchedHost
}

trap {
    Write-RunLog ("ERROR: " + ($_ | Out-String).Trim())
    exit 1
}

function New-TemporarySshKeyCopy {
    param(
        [Parameter(Mandatory = $true)]
        [string]$SourceKeyPath
    )

    $tempDir = Join-Path $env:TEMP 'aw-rus-1c-ssh'
    New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
    $tempKeyPath = Join-Path $tempDir 'awops_ed25519'
    $candidates = New-Object System.Collections.Generic.List[string]
    foreach ($candidate in @(
        $SourceKeyPath,
        (Join-Path $env:USERPROFILE '.ssh\awops_ed25519'),
        (Join-Path ([System.Environment]::GetFolderPath('UserProfile')) '.ssh\awops_ed25519')
    )) {
        if (-not [string]::IsNullOrWhiteSpace($candidate) -and -not $candidates.Contains($candidate)) {
            $candidates.Add($candidate)
        }
    }

    foreach ($candidate in $candidates) {
        $pathError = $null
        $exists = Test-Path -LiteralPath $candidate -ErrorAction SilentlyContinue -ErrorVariable pathError
        if ($pathError) {
            Write-RunLog ("skip unusable ssh key path={0} reason={1}" -f $candidate, $pathError[0].Exception.Message)
            continue
        }
        if (-not $exists) {
            continue
        }

        & cmd.exe /d /c "copy /Y `"$candidate`" `"$tempKeyPath`" >nul"
        if ($LASTEXITCODE -ne 0) {
            Write-RunLog ("skip unusable ssh key path={0} reason=copy command failed rc={1}" -f $candidate, $LASTEXITCODE)
            continue
        }

        & icacls.exe $tempKeyPath /inheritance:r | Out-Null
        & icacls.exe $tempKeyPath /grant:r "$($env:USERNAME):(F)" | Out-Null
        & icacls.exe $tempKeyPath /remove:g 'Users' 'Authenticated Users' 'Everyone' 'BUILTIN\Users' 'BUILTIN\Administrators' 'NT AUTHORITY\SYSTEM' 2>$null | Out-Null
        return $tempKeyPath
    }

    throw "No usable SSH private key found. checked={0}" -f ($candidates -join ', ')
}

function Get-1CFileInfobases {
    $results = New-Object System.Collections.Generic.List[object]
    $launcherFiles = New-Object System.Collections.Generic.List[string]
    Get-ChildItem -Path 'C:\Users' -Directory -ErrorAction SilentlyContinue |
        ForEach-Object {
            $launcherFile = Join-Path $_.FullName 'AppData\Roaming\1C\1CEStart\ibases.v8i'
            try {
                if (Test-Path -LiteralPath $launcherFile -ErrorAction Stop) {
                    $launcherFiles.Add($launcherFile)
                }
            } catch {
                Write-RunLog ("skip inaccessible launcher file={0} reason={1}" -f $launcherFile, $_.Exception.Message)
            }
        }

    foreach ($file in $launcherFiles) {
        $userName = 'unknown'
        if ([string]$file -match '^[A-Za-z]:\\Users\\([^\\]+)\\') {
            $userName = [string]$Matches[1]
        }
        $currentName = $null
        $currentId = $null
        $fileLines = $null
        try {
            $fileLines = Get-Content -LiteralPath $file -Encoding UTF8 -ErrorAction Stop
        } catch {
            Write-RunLog ("skip unreadable launcher file={0} reason={1}" -f $file, $_.Exception.Message)
            continue
        }

        foreach ($lineRaw in $fileLines) {
            $line = [string]$lineRaw
            if ($line -match '^\[(.+)\]$') {
                $currentName = $Matches[1]
                $currentId = $null
                continue
            }
            if ($line -match '^ID=(.+)$') {
                $currentId = $Matches[1].Trim()
                continue
            }
            if ($line -match '^Connect=File="(.+)";$' -and $currentName) {
                $results.Add([pscustomobject]@{
                    userName = $userName
                    infobase = $currentName
                    baseId = $currentId
                    path = $Matches[1]
                    launcherFile = $file
                })
            }
        }
    }

    return $results |
        Group-Object infobase, path |
        ForEach-Object { $_.Group | Select-Object -First 1 }
}

function Get-HostSample {
    $cpu = 0.0
    $ramPct = 0.0
    try {
        Add-Type -AssemblyName Microsoft.VisualBasic -ErrorAction Stop
        $computerInfo = New-Object Microsoft.VisualBasic.Devices.ComputerInfo
        $totalMemory = [double]$computerInfo.TotalPhysicalMemory
        $availableMemory = [double]$computerInfo.AvailablePhysicalMemory
        if ($totalMemory -gt 0) {
            $ramPct = (($totalMemory - $availableMemory) / $totalMemory) * 100
        }
    } catch {
        Write-RunLog ("warning: host memory sample fallback reason={0}" -f $_.Exception.Message)
    }
    $disk = Get-PSDrive -Name E -ErrorAction SilentlyContinue
    $rdp = (quser 2>$null | Select-Object -Skip 1 | Measure-Object).Count

    return [ordered]@{
        ts = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
        host = $env:COMPUTERNAME
        cpu_pct = [math]::Round($cpu, 2)
        ram_pct = [math]::Round($ramPct, 2)
        disk_free_gb = if ($disk) { [math]::Round($disk.Free / 1GB, 2) } else { 0 }
        disk_latency_ms = 0
        smb_errors = 0
        rdp_sessions = $rdp
        backup_ok = 1
    }
}

function Write-JsonLines {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [object]$Rows
    )

    $directory = Split-Path -Parent $Path
    if ($directory) {
        New-Item -ItemType Directory -Path $directory -Force | Out-Null
    }
    $normalizedRows = @()
    if ($null -ne $Rows) {
        $normalizedRows = @($Rows)
    }
    $normalizedRows |
        ForEach-Object { $_ | ConvertTo-Json -Depth 8 -Compress } |
        Set-Content -LiteralPath $Path -Encoding UTF8
}

function Invoke-SshUploadWithRetry {
    param(
        [Parameter(Mandatory = $true)]
        [string]$KeyPath,
        [Parameter(Mandatory = $true)]
        [string]$SourcePath,
        [Parameter(Mandatory = $true)]
        [string]$Destination,
        [int]$Attempts = 3,
        [int]$DelaySeconds = 5
    )

    for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
        Write-RunLog "scp attempt=$attempt source=$SourcePath destination=$Destination"
        & $ScpExe -q -i $KeyPath -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL $SourcePath $Destination
        if ($LASTEXITCODE -eq 0) {
            Write-RunLog "scp success source=$SourcePath"
            return
        }
        if ($attempt -ge $Attempts) {
            throw "scp upload failed after $Attempts attempts for $SourcePath with rc=$LASTEXITCODE"
        }
        Write-RunLog "scp retry source=$SourcePath rc=$LASTEXITCODE delay=${DelaySeconds}s"
        Start-Sleep -Seconds $DelaySeconds
    }
}

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

    if (-not (Test-Path -LiteralPath $Path)) {
        return @{}
    }

    $raw = Get-Content -Raw -LiteralPath $Path -Encoding UTF8
    if ([string]::IsNullOrWhiteSpace($raw)) {
        return @{}
    }

    $payload = $raw | ConvertFrom-Json
    $state = @{}
    foreach ($prop in $payload.PSObject.Properties) {
        $state[[string]$prop.Name] = $prop.Value
    }
    return $state
}

function Save-ExporterState {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [hashtable]$State
    )

    $directory = Split-Path -Parent $Path
    if ($directory) {
        New-Item -ItemType Directory -Path $directory -Force | Out-Null
    }
    try {
        $State | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $Path -Encoding UTF8
    } catch {
        Write-RunLog ("warning: failed to persist exporter state path={0} reason={1}" -f $Path, $_.Exception.Message)
    }
}

function Get-CompanyActivityScore {
    param(
        [double]$DbDeltaMb,
        [double]$ReglogDeltaMb,
        [int]$ActiveLocks,
        [bool]$HasTempDb,
        [bool]$SchedulerTouched,
        [string]$Status,
        [bool]$IsBootstrap
    )

    $score = 0.0
    $score += [math]::Abs($DbDeltaMb)
    $score += [math]::Abs($ReglogDeltaMb)
    if ($ActiveLocks -gt 0) {
        $score += ($ActiveLocks * 5)
    }
    if ($HasTempDb) {
        $score += 10
    }
    if ($SchedulerTouched) {
        $score += 3
    }
    if ($Status -eq 'busy') {
        $score += 5
    }
    if ($IsBootstrap -and $score -le 0) {
        $score = 1
    }
    return [math]::Round($score, 2)
}

Write-RunLog 'file1c exporter start'

if (-not (Test-Path -LiteralPath $ScpExe)) {
    throw "scp client not found: $ScpExe"
}

$config = Get-Content -Raw -LiteralPath $ConfigPath | ConvertFrom-Json
if ($config.PSObject.Properties.Name -contains 'analytics' -and
    $config.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and
    $config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'remoteKeyPath' -and
    -not [string]::IsNullOrWhiteSpace([string]$config.analytics.file1cAutomation.remoteKeyPath)) {
    $RemoteKeyPath = [string]$config.analytics.file1cAutomation.remoteKeyPath
}
if ([string]::IsNullOrWhiteSpace($AnalyticsHost)) {
    if ($config.PSObject.Properties.Name -contains 'analytics' -and
        $config.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and
        $config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'targetHost') {
        $AnalyticsHost = [string]$config.analytics.file1cAutomation.targetHost
    }
}
if ([string]::IsNullOrWhiteSpace($AnalyticsHost)) {
    $AnalyticsHost = Get-LastSuccessfulAnalyticsHostFromRunLog -Path $LogPath
    if (-not [string]::IsNullOrWhiteSpace($AnalyticsHost)) {
        Write-RunLog "recovered analyticsHost=$AnalyticsHost from previous successful uploader log"
        try {
            if ($config.PSObject.Properties.Name -notcontains 'analytics' -or $null -eq $config.analytics) {
                if ($config.PSObject.Properties.Name -contains 'analytics') {
                    $config.analytics = [pscustomobject]@{}
                } else {
                    $config | Add-Member -NotePropertyName 'analytics' -NotePropertyValue ([pscustomobject]@{})
                }
            }
            if ($config.analytics.PSObject.Properties.Name -notcontains 'file1cAutomation' -or $null -eq $config.analytics.file1cAutomation) {
                if ($config.analytics.PSObject.Properties.Name -contains 'file1cAutomation') {
                    $config.analytics.file1cAutomation = [pscustomobject]@{}
                } else {
                    $config.analytics | Add-Member -NotePropertyName 'file1cAutomation' -NotePropertyValue ([pscustomobject]@{})
                }
            }
            if ($config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'targetHost') {
                $config.analytics.file1cAutomation.targetHost = $AnalyticsHost
            } else {
                $config.analytics.file1cAutomation | Add-Member -NotePropertyName 'targetHost' -NotePropertyValue $AnalyticsHost
            }
            Save-DeploymentConfig -Config $config -Path $ConfigPath
        } catch {
            Write-RunLog ("warning: failed to persist recovered analyticsHost into deployment-config: " + $_.Exception.Message)
        }
    }
}
if ([string]::IsNullOrWhiteSpace($AnalyticsHost)) {
    throw "AnalyticsHost is empty, deployment-config has no analytics.file1cAutomation.targetHost, and no previous successful uploader log was found"
}
if ($config.PSObject.Properties.Name -contains 'analytics' -and
    $config.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and
    $config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'targetUser' -and
    -not [string]::IsNullOrWhiteSpace([string]$config.analytics.file1cAutomation.targetUser)) {
    $AnalyticsUser = [string]$config.analytics.file1cAutomation.targetUser
}
if ($config.PSObject.Properties.Name -contains 'analytics' -and
    $config.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and
    $config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'remoteRoot' -and
    -not [string]::IsNullOrWhiteSpace([string]$config.analytics.file1cAutomation.remoteRoot)) {
    $RemoteRoot = [string]$config.analytics.file1cAutomation.remoteRoot
}
if ([string]::IsNullOrWhiteSpace($RegistryWorkbookPath) -and
    $config.PSObject.Properties.Name -contains 'analytics' -and
    $config.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and
    $config.analytics.file1cAutomation.PSObject.Properties.Name -contains 'registryWorkbookPath' -and
    -not [string]::IsNullOrWhiteSpace([string]$config.analytics.file1cAutomation.registryWorkbookPath)) {
    $RegistryWorkbookPath = [string]$config.analytics.file1cAutomation.registryWorkbookPath
}
if ([string]::IsNullOrWhiteSpace($RegistryWorkbookPath)) {
    $RegistryWorkbookPath = 'E:\USER1\СПИСОК ПРЕДПРИЯТИЙ И ИХ РАСПРЕДЕЛЕНИЕ.xlsx'
}

$infobases = @(Get-1CFileInfobases)
$nowUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
$exporterState = Get-ExporterState -Path $ExporterStatePath
$nextExporterState = @{}
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'

$documents = New-Object System.Collections.Generic.List[object]
$companies = New-Object System.Collections.Generic.List[object]
$reglog = New-Object System.Collections.Generic.List[object]
$audit = New-Object System.Collections.Generic.List[object]

foreach ($base in $infobases) {
    $dbFile = Join-Path $base.path '1Cv8.1CD'
    $dbItem = Get-Item -LiteralPath $dbFile -ErrorAction SilentlyContinue
    $logDir = Join-Path $base.path '1Cv8Log'
    $logItems = @(Get-ChildItem -LiteralPath $logDir -File -ErrorAction SilentlyContinue)
    $mainLog = $logItems | Where-Object { $_.Extension -ieq '.lgp' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1
    $activeLocks = @(Get-ChildItem -LiteralPath $base.path -File -Filter '1Cv8*.1CL*' -ErrorAction SilentlyContinue)
    $tempDb = Get-Item -LiteralPath (Join-Path $base.path '1Cv8tmp.1CD') -ErrorAction SilentlyContinue
    $schedulerDir = Get-Item -LiteralPath (Join-Path $base.path '1Cv8JobScheduler') -ErrorAction SilentlyContinue
    $owner = if ($base.userName) { [string]$base.userName } else { 'unknown' }
    $organization = [string](Split-Path -Leaf (Split-Path -Parent $base.path))
    $status = if ($activeLocks.Count -gt 0 -or $tempDb) { 'busy' } else { 'online' }
    $docId = if ($base.baseId) { [string]$base.baseId } else { ([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$base.path)).TrimEnd('=').Replace('/','_').Replace('+','-')) }
    $stateKey = [string]$docId
    $previous = if ($exporterState.ContainsKey($stateKey)) { $exporterState[$stateKey] } else { $null }
    $dbSizeBytes = if ($dbItem) { [int64]$dbItem.Length } else { 0 }
    $mainLogBytes = if ($mainLog) { [int64]$mainLog.Length } else { 0 }
    $schedulerWriteUtc = if ($schedulerDir) { [datetime]$schedulerDir.LastWriteTimeUtc } else { $null }
    $dbDeltaMb = 0.0
    $reglogDeltaMb = 0.0
    $schedulerTouched = $false
    $isBootstrap = ($null -eq $previous)

    if (-not $isBootstrap) {
        $prevDbSize = if ($previous.PSObject.Properties.Name -contains 'dbSizeBytes') { [double]$previous.dbSizeBytes } else { 0 }
        $prevMainLogSize = if ($previous.PSObject.Properties.Name -contains 'mainLogBytes') { [double]$previous.mainLogBytes } else { 0 }
        $dbDeltaMb = [math]::Round(($dbSizeBytes - $prevDbSize) / 1MB, 2)
        $reglogDeltaMb = [math]::Round(($mainLogBytes - $prevMainLogSize) / 1MB, 2)
        if ($schedulerWriteUtc -and $previous.PSObject.Properties.Name -contains 'schedulerWriteUtc' -and -not [string]::IsNullOrWhiteSpace([string]$previous.schedulerWriteUtc)) {
            $prevSchedulerWriteUtc = [datetime]::Parse([string]$previous.schedulerWriteUtc)
            $schedulerTouched = ($schedulerWriteUtc -gt $prevSchedulerWriteUtc)
        }
        elseif ($schedulerWriteUtc) {
            $schedulerTouched = $true
        }
    }

    $activityScore = Get-CompanyActivityScore `
        -DbDeltaMb $dbDeltaMb `
        -ReglogDeltaMb $reglogDeltaMb `
        -ActiveLocks $activeLocks.Count `
        -HasTempDb ([bool]$tempDb) `
        -SchedulerTouched $schedulerTouched `
        -Status $status `
        -IsBootstrap $isBootstrap

    $documents.Add([ordered]@{
        ts = $nowUtc
        infobase = [string]$base.infobase
        organization = $organization
        department = 'FileBase'
        doc_type = 'InfobaseSnapshot'
        doc_id = $docId
        doc_number = ''
        author = $owner
        counterparty = ''
        operation_type = 'inventory'
        amount = 0
        status = $status
        posted = 1
    })

    $companies.Add([ordered]@{
        ts = $nowUtc
        infobase = [string]$base.infobase
        company_name = [string]$base.infobase
        organization = $organization
        owner_user = $owner
        base_id = $docId
        base_path = [string]$base.path
        status = $status
        db_size_bytes = $dbSizeBytes
        reglog_size_bytes = $mainLogBytes
        active_locks = $activeLocks.Count
        temp_db_present = if ($tempDb) { 1 } else { 0 }
        scheduler_touched = if ($schedulerTouched) { 1 } else { 0 }
        activity_score = $activityScore
    })

    if ($activityScore -gt 0) {
        $documents.Add([ordered]@{
            ts = $nowUtc
            infobase = [string]$base.infobase
            organization = $organization
            department = 'FileBaseActivity'
            doc_type = 'CompanyActivitySnapshot'
            doc_id = "$docId-$stamp"
            doc_number = $stamp
            author = $owner
            counterparty = [string]$base.infobase
            operation_type = 'activity_snapshot'
            amount = $activityScore
            status = $status
            posted = 1
        })
    }

    $audit.Add([ordered]@{
        ts = $nowUtc
        infobase = [string]$base.infobase
        user = $owner
        object_type = 'infobase'
        object_id = $docId
        action = 'inventory_snapshot'
        before_hash = ''
        after_hash = ''
        risk_tag = if ($status -eq 'busy') { 'busy' } else { '' }
    })

    if ($mainLog) {
        $reglog.Add([ordered]@{
            ts = ([datetime]$mainLog.LastWriteTimeUtc).ToString('yyyy-MM-ddTHH:mm:ssZ')
            infobase = [string]$base.infobase
            user = $owner
            host = $env:COMPUTERNAME
            app = '1cv8-file'
            event_name = 'RegLogInventory'
            level = if ($mainLog.Length -gt 536870912) { 'warn' } else { 'info' }
            duration_ms = 0
            message = "Registration log file $($mainLog.Name) size=$([math]::Round($mainLog.Length / 1MB, 2))MB path=$($mainLog.FullName)"
        })
    }

    if ($activeLocks.Count -gt 0 -or $tempDb) {
        $reglog.Add([ordered]@{
            ts = $nowUtc
            infobase = [string]$base.infobase
            user = $owner
            host = $env:COMPUTERNAME
            app = '1cv8-file'
            event_name = 'FileBaseBusy'
            level = 'warn'
            duration_ms = 0
            message = "Detected active file-base markers: locks=$($activeLocks.Count) tempDb=$([bool]$tempDb)"
        })
    }

    if ($schedulerDir) {
        $reglog.Add([ordered]@{
            ts = ([datetime]$schedulerDir.LastWriteTimeUtc).ToString('yyyy-MM-ddTHH:mm:ssZ')
            infobase = [string]$base.infobase
            user = $owner
            host = $env:COMPUTERNAME
            app = '1cv8-file'
            event_name = 'JobSchedulerActivity'
            level = 'info'
            duration_ms = 0
            message = "1Cv8JobScheduler touched at $([datetime]$schedulerDir.LastWriteTimeUtc)"
        })
    }

    $reglog.Add([ordered]@{
        ts = $nowUtc
        infobase = [string]$base.infobase
        user = $owner
        host = $env:COMPUTERNAME
        app = '1cv8-file'
        event_name = 'CompanyActivitySnapshot'
        level = if ($activityScore -gt 20) { 'warn' } else { 'info' }
        duration_ms = 0
        message = "activityScore=$activityScore dbDeltaMb=$dbDeltaMb reglogDeltaMb=$reglogDeltaMb locks=$($activeLocks.Count) tempDb=$([bool]$tempDb) schedulerTouched=$schedulerTouched"
    })

    $nextExporterState[$stateKey] = [ordered]@{
        infobase = [string]$base.infobase
        path = [string]$base.path
        dbSizeBytes = $dbSizeBytes
        mainLogBytes = $mainLogBytes
        schedulerWriteUtc = if ($schedulerWriteUtc) { $schedulerWriteUtc.ToString('o') } else { '' }
        updatedAtUtc = $nowUtc
    }
}

$hostRows = @((Get-HostSample))

$outRoot = Join-Path $env:TEMP "aw-rus-1c-outbox-$stamp"
New-Item -ItemType Directory -Path $outRoot -Force | Out-Null

$files = @{
    documents = Join-Path $outRoot "documents-$stamp.jsonl"
    companies = Join-Path $outRoot "companies-$stamp.jsonl"
    reglog = Join-Path $outRoot "reglog-$stamp.jsonl"
    audit = Join-Path $outRoot "audit-$stamp.jsonl"
    host = Join-Path $outRoot "host-$stamp.jsonl"
    registry = Join-Path $outRoot "company-registry-$stamp.xlsx"
}

$documentRows = @($documents | ForEach-Object { $_ })
$companyRows = @($companies | ForEach-Object { $_ })
$reglogRows = @($reglog | ForEach-Object { $_ })
$auditRows = @($audit | ForEach-Object { $_ })
$hostRowsNormalized = @($hostRows | ForEach-Object { $_ })

Write-JsonLines -Path ([string]$files['documents']) -Rows $documentRows
Write-JsonLines -Path ([string]$files['companies']) -Rows $companyRows
Write-JsonLines -Path ([string]$files['reglog']) -Rows $reglogRows
Write-JsonLines -Path ([string]$files['audit']) -Rows $auditRows
Write-JsonLines -Path ([string]$files['host']) -Rows $hostRowsNormalized

$registryUploaded = $false
if (Test-Path -LiteralPath $RegistryWorkbookPath) {
    Copy-Item -LiteralPath $RegistryWorkbookPath -Destination ([string]$files['registry']) -Force
}

$effectiveKeyPath = New-TemporarySshKeyCopy -SourceKeyPath $RemoteKeyPath

try {
    Write-RunLog "prepared datasets documents=$($documentRows.Count) companies=$($companyRows.Count) reglog=$($reglogRows.Count) audit=$($auditRows.Count) host=$($hostRowsNormalized.Count)"
    foreach ($dataset in 'documents', 'companies', 'reglog', 'audit', 'host') {
        Invoke-SshUploadWithRetry -KeyPath $effectiveKeyPath -SourcePath ([string]$files[$dataset]) -Destination "$AnalyticsUser@$AnalyticsHost`:$RemoteRoot/$dataset/"
    }
    if (Test-Path -LiteralPath ([string]$files['registry'])) {
        Invoke-SshUploadWithRetry -KeyPath $effectiveKeyPath -SourcePath ([string]$files['registry']) -Destination "$AnalyticsUser@$AnalyticsHost`:$RemoteRoot/registry/"
        $registryUploaded = $true
    }
    Save-ExporterState -Path $ExporterStatePath -State $nextExporterState
    Write-RunLog "upload complete analyticsHost=$AnalyticsHost remoteRoot=$RemoteRoot"
}
finally {
    Remove-Item -LiteralPath $effectiveKeyPath -Force -ErrorAction SilentlyContinue
    Remove-Item -LiteralPath $outRoot -Recurse -Force -ErrorAction SilentlyContinue
}

Write-RunLog 'file1c exporter done'

[ordered]@{
    analyticsHost = $AnalyticsHost
    analyticsUser = $AnalyticsUser
    remoteRoot = $RemoteRoot
    infobases = @($infobases | ForEach-Object { $_.infobase })
    datasets = [ordered]@{
        documents = $documents.Count
        companies = $companies.Count
        reglog = $reglog.Count
        audit = $audit.Count
        host = $hostRows.Count
        registry = if ($registryUploaded) { 1 } else { 0 }
    }
} | ConvertTo-Json -Depth 8
