param(
    [Parameter(Mandatory = $true, ParameterSetName = 'DryRun')]
    [switch]$DryRun,

    [Parameter(Mandatory = $true, ParameterSetName = 'Execute')]
    [switch]$Execute,

    [Parameter(Mandatory = $true, ParameterSetName = 'Execute')]
    [string]$AuthorizationCode,

    [Parameter(ParameterSetName = 'Execute')]
    [switch]$Resume
)

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

$campaignId = 'AIBLACK-300-20260830T183052882Z'
$endpoint = 'https://lanxas-ia.145.239.141.177.sslip.io/black-chat-json'
$timeoutSeconds = 1500
$requiredAuthorization = 'FEU_VERT_MADAME_AURELIE_Q001_Q300'
$packRoot = 'C:\Users\aurel\Documents\lanxas1\AIBLACK_BENCHMARK_V1'
$runnerRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$outRoot = Join-Path $runnerRoot 'RUNNER_OUT'
$responsesRoot = Join-Path $outRoot 'reponses'
$attemptsRoot = Join-Path $outRoot 'attempts'
$csvPath = Join-Path $outRoot 'campagne_log.csv'
$finalHashesPath = Join-Path $outRoot 'SHA256_REPONSES.txt'
$startedPath = Join-Path $runnerRoot 'CAMPAIGN_EXECUTION_STARTED.json'
$activeLockPath = Join-Path $runnerRoot 'CAMPAIGN_ACTIVE.lock'
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
$invariant = [System.Globalization.CultureInfo]::InvariantCulture
$csvHeader = 'id;timestamp_utc_envoi;http_status;latence_s;taille_octets;sha256_reponse'

function Get-Sha256Hex {
    param([byte[]]$Bytes)
    return [Convert]::ToHexString(
        [System.Security.Cryptography.SHA256]::HashData($Bytes)
    ).ToLowerInvariant()
}

function Write-NewBytes {
    param(
        [string]$LiteralPath,
        [byte[]]$Bytes
    )
    $stream = [System.IO.File]::Open(
        $LiteralPath,
        [System.IO.FileMode]::CreateNew,
        [System.IO.FileAccess]::Write,
        [System.IO.FileShare]::None
    )
    try {
        $stream.Write($Bytes, 0, $Bytes.Length)
        $stream.Flush($true)
    }
    finally {
        $stream.Dispose()
    }
}

function Append-CsvResult {
    param(
        [string]$Id,
        [string]$TimestampUtc,
        [string]$HttpStatus,
        [string]$LatencySeconds,
        [long]$SizeBytes,
        [string]$ResponseSha256
    )
    $line = "$Id;$TimestampUtc;$HttpStatus;$LatencySeconds;$SizeBytes;$ResponseSha256`n"
    [System.IO.File]::AppendAllText($csvPath, $line, $utf8NoBom)
}

function Read-QuestionsFromSealedPack {
    $verifyPath = Join-Path $packRoot 'VERIFY_V1.ps1'
    if (-not (Test-Path -LiteralPath $verifyPath -PathType Leaf)) {
        throw 'SEALED_VERIFY_SCRIPT_MISSING'
    }
    $verifyOutput = (@(& $verifyPath 2>&1) -join "`n").Trim()
    if ($verifyOutput -ne 'VERIFY_OK') {
        throw "SEALED_PACK_VERIFY_FAILED:$verifyOutput"
    }

    $manifest = Get-Content -Raw -LiteralPath (Join-Path $packRoot 'MANIFEST_V1.json') | ConvertFrom-Json
    if ($manifest.campaign_id -ne $campaignId) {
        throw 'CAMPAIGN_ID_MISMATCH'
    }

    $questions = [System.Collections.Generic.List[object]]::new()
    foreach ($spec in @(
        [pscustomobject]@{ Level = '5A'; First = 1; Last = 100 },
        [pscustomobject]@{ Level = '5B'; First = 101; Last = 200 },
        [pscustomobject]@{ Level = '5C'; First = 201; Last = 300 }
    )) {
        $path = Join-Path $packRoot "questions_level_$($spec.Level).tsv"
        $lines = [System.IO.File]::ReadAllLines($path, $utf8NoBom)
        if ($lines.Count -ne 100) {
            throw "QUESTION_COUNT_INVALID:$($spec.Level)"
        }
        for ($number = $spec.First; $number -le $spec.Last; $number++) {
            $index = $number - $spec.First
            $fields = $lines[$index].Split("`t")
            $expectedId = 'Q{0:D3}' -f $number
            if ($fields.Count -ne 7) {
                throw "QUESTION_TSV_INVALID:$expectedId"
            }
            if ($fields[0] -ne $expectedId -or $fields[1] -ne $spec.Level -or $fields[2] -ne '4') {
                throw "QUESTION_METADATA_INVALID:$expectedId"
            }
            if ([string]::IsNullOrWhiteSpace($fields[3])) {
                throw "QUESTION_TEXT_EMPTY:$expectedId"
            }
            $questions.Add([pscustomobject]@{
                Id = $expectedId
                Number = $number
                Text = $fields[3]
            })
        }
    }

    if ($questions.Count -ne 300) {
        throw 'TOTAL_QUESTION_COUNT_INVALID'
    }
    if (($questions.Id | Sort-Object -Unique).Count -ne 300) {
        throw 'QUESTION_IDS_NOT_UNIQUE'
    }
    return $questions
}

function Read-CampaignLog {
    if (-not (Test-Path -LiteralPath $csvPath -PathType Leaf)) {
        throw 'CAMPAIGN_LOG_MISSING'
    }
    $lines = [System.IO.File]::ReadAllLines($csvPath, $utf8NoBom)
    if ($lines.Count -lt 1 -or $lines[0] -ne $csvHeader) {
        throw 'CAMPAIGN_LOG_HEADER_INVALID'
    }
    if ($lines.Count -eq 1) {
        return @()
    }
    return @(Import-Csv -LiteralPath $csvPath -Delimiter ';')
}

function Confirm-CompletedRows {
    param(
        [object[]]$Questions,
        [object[]]$LogRows
    )

    $qRows = @($LogRows | Where-Object { $_.id -match '^Q\d{3}$' })
    $qIds = @($qRows | ForEach-Object { $_.id })
    if ((@($qIds | Sort-Object -Unique)).Count -ne $qRows.Count) {
        throw 'DUPLICATE_Q_LOG_ROW'
    }

    for ($index = 0; $index -lt $qRows.Count; $index++) {
        $expected = $Questions[$index]
        $row = $qRows[$index]
        if ($row.id -ne $expected.Id) {
            throw "NON_SEQUENTIAL_LOG:$($row.id):EXPECTED:$($expected.Id)"
        }
        $responsePath = Join-Path $responsesRoot ($row.id + '.json')
        if (-not (Test-Path -LiteralPath $responsePath -PathType Leaf)) {
            throw "LOGGED_RESPONSE_MISSING:$($row.id)"
        }
        $bytes = [System.IO.File]::ReadAllBytes($responsePath)
        $actualHash = Get-Sha256Hex -Bytes $bytes
        if ($actualHash -ne $row.sha256_reponse) {
            throw "LOGGED_RESPONSE_HASH_MISMATCH:$($row.id)"
        }
        if ([long]$row.taille_octets -ne $bytes.Length) {
            throw "LOGGED_RESPONSE_SIZE_MISMATCH:$($row.id)"
        }
    }

    $unloggedFiles = @(
        Get-ChildItem -LiteralPath $responsesRoot -File -Filter 'Q???.json' |
            Where-Object { $qIds -notcontains $_.BaseName }
    )
    foreach ($file in $unloggedFiles) {
        $markerPath = Join-Path $attemptsRoot ($file.BaseName + '.started.json')
        if (-not (Test-Path -LiteralPath $markerPath -PathType Leaf)) {
            throw "UNLOGGED_RESPONSE_WITHOUT_MARKER:$($file.BaseName)"
        }
    }

    return $qRows.Count
}

function Complete-InterruptedAttemptWithoutReplay {
    param([object]$Question)

    $markerPath = Join-Path $attemptsRoot ($Question.Id + '.started.json')
    $responsePath = Join-Path $responsesRoot ($Question.Id + '.json')
    if (-not (Test-Path -LiteralPath $markerPath -PathType Leaf)) {
        return $false
    }

    $marker = Get-Content -Raw -LiteralPath $markerPath | ConvertFrom-Json
    if ($marker.id -ne $Question.Id) {
        throw "ATTEMPT_MARKER_ID_MISMATCH:$($Question.Id)"
    }
    $timestamp = ([DateTime]$marker.timestamp_utc).ToUniversalTime().ToString(
        'yyyy-MM-ddTHH:mm:ss.fffZ',
        $invariant
    )
    if (-not (Test-Path -LiteralPath $responsePath -PathType Leaf)) {
        Write-NewBytes -LiteralPath $responsePath -Bytes ([byte[]]::new(0))
    }
    $bytes = [System.IO.File]::ReadAllBytes($responsePath)
    $hash = Get-Sha256Hex -Bytes $bytes
    Append-CsvResult -Id $Question.Id -TimestampUtc $timestamp -HttpStatus 'UNKNOWN_INTERRUPTED_NO_REPLAY' -LatencySeconds '0.000000' -SizeBytes $bytes.Length -ResponseSha256 $hash
    return $true
}

$questions = @(Read-QuestionsFromSealedPack)

if (-not (Test-Path -LiteralPath $outRoot -PathType Container)) {
    throw 'RUNNER_OUT_MISSING_TEST_BLANK_REQUIRED'
}
if (-not (Test-Path -LiteralPath $responsesRoot -PathType Container)) {
    throw 'RESPONSES_DIRECTORY_MISSING'
}
$logRows = @(Read-CampaignLog)
$tbRows = @($logRows | Where-Object { $_.id -match '^TB_R[12]$' })
if ($tbRows.Count -ne 2) {
    throw 'VALIDATED_TEST_BLANK_ROWS_REQUIRED'
}
$completedCount = Confirm-CompletedRows -Questions $questions -LogRows $logRows

if ($DryRun) {
    if ($completedCount -ne 0) {
        throw 'DRY_RUN_EXPECTED_NO_REAL_QUESTION'
    }
    if (Test-Path -LiteralPath $startedPath) {
        throw 'DRY_RUN_CAMPAIGN_ALREADY_STARTED'
    }
    if (Test-Path -LiteralPath $finalHashesPath) {
        throw 'DRY_RUN_FINAL_HASH_FILE_ALREADY_EXISTS'
    }
    Write-Output "DRY_RUN_OK campaign_id=$campaignId questions=300 next=Q001 network_calls=0"
    exit 0
}

if (-not $Execute) {
    throw 'EXECUTION_MODE_REQUIRED'
}
if ($AuthorizationCode -cne $requiredAuthorization) {
    throw 'EXPLICIT_AUTHORIZATION_CODE_INVALID'
}

$startedExists = Test-Path -LiteralPath $startedPath -PathType Leaf
if ($Resume -and -not $startedExists) {
    throw 'RESUME_WITHOUT_STARTED_MARKER'
}
if (-not $Resume -and $startedExists) {
    throw 'CAMPAIGN_ALREADY_STARTED_USE_EXPLICIT_RESUME'
}
if (-not $Resume -and $completedCount -ne 0) {
    throw 'CAMPAIGN_LOG_ALREADY_HAS_REAL_QUESTIONS'
}
if (Test-Path -LiteralPath $finalHashesPath) {
    throw 'CAMPAIGN_ALREADY_FINALIZED'
}

$activeStream = [System.IO.File]::Open(
    $activeLockPath,
    [System.IO.FileMode]::CreateNew,
    [System.IO.FileAccess]::Write,
    [System.IO.FileShare]::None
)
$handler = $null
$client = $null
try {
    $activeBytes = $utf8NoBom.GetBytes(
        "campaign_id=$campaignId`npid=$PID`nstarted_utc=$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ', $invariant))`n"
    )
    $activeStream.Write($activeBytes, 0, $activeBytes.Length)
    $activeStream.Flush($true)
}
finally {
    $activeStream.Dispose()
}

try {
    if (-not $startedExists) {
        $startedObject = [ordered]@{
            campaign_id = $campaignId
            started_utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ', $invariant)
            authorization = $requiredAuthorization
            max_attempts = 1
            timeout_seconds = $timeoutSeconds
        }
        $startedBytes = $utf8NoBom.GetBytes(($startedObject | ConvertTo-Json -Compress))
        Write-NewBytes -LiteralPath $startedPath -Bytes $startedBytes
    }

    [System.IO.Directory]::CreateDirectory($attemptsRoot) | Out-Null

    $handler = [System.Net.Http.HttpClientHandler]::new()
    $handler.AllowAutoRedirect = $false
    $handler.UseCookies = $false
    $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::None
    $client = [System.Net.Http.HttpClient]::new($handler, $true)
    $client.Timeout = [System.TimeSpan]::FromSeconds($timeoutSeconds)

    while ($completedCount -lt $questions.Count) {
        $question = $questions[$completedCount]

        if (Complete-InterruptedAttemptWithoutReplay -Question $question) {
            $completedCount++
            continue
        }

        $responsePath = Join-Path $responsesRoot ($question.Id + '.json')
        $markerPath = Join-Path $attemptsRoot ($question.Id + '.started.json')
        if (Test-Path -LiteralPath $responsePath) {
            throw "RESPONSE_EXISTS_BEFORE_ATTEMPT:$($question.Id)"
        }

        $requestObject = [ordered]@{
            messages = @([ordered]@{ role = 'user'; content = $question.Text })
            mode = 'black'
            power = 3
        }
        $requestJson = $requestObject | ConvertTo-Json -Compress -Depth 5
        $requestBytes = $utf8NoBom.GetBytes($requestJson)
        $timestampUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ', $invariant)

        $markerObject = [ordered]@{
            id = $question.Id
            timestamp_utc = $timestampUtc
            request_sha256 = Get-Sha256Hex -Bytes $requestBytes
            endpoint = $endpoint
            max_attempts = 1
        }
        $markerBytes = $utf8NoBom.GetBytes(($markerObject | ConvertTo-Json -Compress))
        Write-NewBytes -LiteralPath $markerPath -Bytes $markerBytes

        $content = [System.Net.Http.ByteArrayContent]::new($requestBytes)
        $content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new('application/json')
        $content.Headers.ContentType.CharSet = 'utf-8'
        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
        $status = 'CLIENT_ERROR'
        $rawResponse = [byte[]]::new(0)

        try {
            $response = $client.PostAsync($endpoint, $content).GetAwaiter().GetResult()
            try {
                $status = ([int]$response.StatusCode).ToString($invariant)
                $rawResponse = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
            }
            finally {
                $response.Dispose()
            }
        }
        catch [System.Management.Automation.PipelineStoppedException] {
            throw
        }
        catch [System.Threading.Tasks.TaskCanceledException] {
            $status = 'TIMEOUT'
        }
        catch [System.Net.Http.HttpRequestException] {
            $status = 'NETWORK_ERROR'
        }
        catch {
            $status = 'CLIENT_ERROR'
        }
        finally {
            $stopwatch.Stop()
            $content.Dispose()
        }

        Write-NewBytes -LiteralPath $responsePath -Bytes $rawResponse
        $responseHash = Get-Sha256Hex -Bytes $rawResponse
        $latency = $stopwatch.Elapsed.TotalSeconds.ToString('F6', $invariant)
        Append-CsvResult -Id $question.Id -TimestampUtc $timestampUtc -HttpStatus $status -LatencySeconds $latency -SizeBytes $rawResponse.Length -ResponseSha256 $responseHash

        $completedCount++
    }

    $finalLines = [System.Collections.Generic.List[string]]::new()
    foreach ($question in $questions) {
        $responsePath = Join-Path $responsesRoot ($question.Id + '.json')
        if (-not (Test-Path -LiteralPath $responsePath -PathType Leaf)) {
            throw "FINAL_RESPONSE_MISSING:$($question.Id)"
        }
        $hash = (Get-FileHash -LiteralPath $responsePath -Algorithm SHA256).Hash.ToLowerInvariant()
        $finalLines.Add("$hash  reponses/$($question.Id).json")
    }
    if ($finalLines.Count -ne 300) {
        throw 'FINAL_HASH_COUNT_INVALID'
    }
    $finalBytes = $utf8NoBom.GetBytes(($finalLines -join "`n") + "`n")
    Write-NewBytes -LiteralPath $finalHashesPath -Bytes $finalBytes
    Write-Output "CAMPAIGN_RUN_DONE campaign_id=$campaignId responses=300"
}
finally {
    if ($null -ne $client) {
        $client.Dispose()
    }
    elseif ($null -ne $handler) {
        $handler.Dispose()
    }
    if (Test-Path -LiteralPath $activeLockPath -PathType Leaf) {
        Remove-Item -LiteralPath $activeLockPath -Force
    }
}
