param(
    [string]$Server = $env:ONEC_SQL_SERVER,
    [string]$Database = $env:ONEC_SQL_DATABASE,
    [string]$User = $env:ONEC_SQL_USER,
    [string]$Password = $env:ONEC_SQL_PASSWORD,
    [string]$SqlPath = "reports/1c-sql/upo_test/prepare-saved-state-copy.sql",
    [string]$SqlPlanReport = "reports/1c-sql/upo_test/prepare-saved-state-copy-sql.json",
    [string]$PlanPath = "reports/1c-sql/upo_test/saved-state-copy-plan.json",
    [string]$BaseUrl = "http://docker.cin.su:8011",
    [string]$ExpectedBaseId,
    [ValidateSet("ConfigSave", "ConfigCASSave")]
    [string]$ExpectedTargetTable = "ConfigSave",
    [int]$TimeoutSec = 120,
    [string]$Report,
    [string]$VerifyReport,
    [switch]$IUnderstandThisWritesToSql
)

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

if (-not $IUnderstandThisWritesToSql) {
    throw "Refusing to execute SQL without -IUnderstandThisWritesToSql."
}
if (-not $Server) { throw "Server is required. Use -Server or ONEC_SQL_SERVER." }
if (-not $Database) { throw "Database is required. Use -Database or ONEC_SQL_DATABASE." }
if (-not $User) { throw "User is required. Use -User or ONEC_SQL_USER." }
if (-not $Password) { throw "Password is required. Use -Password or ONEC_SQL_PASSWORD." }
if (-not $SqlPath) { throw "SqlPath is required." }
if (-not $SqlPlanReport) { throw "SqlPlanReport is required." }
if (-not $PlanPath) { throw "PlanPath is required." }
if (-not $ExpectedBaseId) { throw "ExpectedBaseId is required." }

function Resolve-RequiredPath {
    param(
        [string]$Label,
        [string]$Path
    )
    if (-not (Test-Path -LiteralPath $Path)) {
        throw "$Label does not exist: $Path"
    }
    return (Resolve-Path -LiteralPath $Path).ProviderPath
}

function Test-HasProperty {
    param(
        [object]$Object,
        [string]$Name
    )
    return $null -ne $Object -and ($Object.PSObject.Properties.Name -contains $Name)
}

function Assert-PlanReport {
    param(
        [object]$PlanReport,
        [string]$ResolvedSqlPath
    )
    if ($PlanReport.schema -ne "onec_saved_state_copy_sql_plan.v1") {
        throw "SqlPlanReport schema must be onec_saved_state_copy_sql_plan.v1."
    }
    if ($PlanReport.status -ne "ready") {
        throw "SqlPlanReport status must be ready."
    }
    if ($PlanReport.read_only -ne $true -or $PlanReport.sql_write_performed -ne $false) {
        throw "SqlPlanReport must describe a read-only generated artifact with sql_write_performed=false."
    }
    if ($PlanReport.base_id -ne $ExpectedBaseId) {
        throw "SqlPlanReport base_id must be $ExpectedBaseId."
    }
    if ($PlanReport.target_table -ne $ExpectedTargetTable) {
        throw "SqlPlanReport target_table must be $ExpectedTargetTable."
    }
    $expectedSource = if ($ExpectedTargetTable -eq "ConfigSave") { "Config" } else { "ConfigCAS" }
    if ($PlanReport.source_table -ne $expectedSource) {
        throw "SqlPlanReport source_table must be $expectedSource for $ExpectedTargetTable."
    }
    if (-not (Test-HasProperty $PlanReport "expected_insert_rows") -or [int]$PlanReport.expected_insert_rows -le 0) {
        throw "SqlPlanReport expected_insert_rows must be positive."
    }
    if ((Test-HasProperty $PlanReport "failures") -and @($PlanReport.failures).Count -gt 0) {
        throw "SqlPlanReport contains failures."
    }
    if ((Test-HasProperty $PlanReport "sql_path") -and $PlanReport.sql_path) {
        $reported = (Resolve-Path -LiteralPath ([string]$PlanReport.sql_path)).ProviderPath
        if ($reported -ne $ResolvedSqlPath) {
            throw "SqlPlanReport sql_path does not match SqlPath."
        }
    }
}

function Assert-GeneratedSql {
    param([string]$SqlText)
    foreach ($needle in @(
        "Generated by scripts/prepare_1c_saved_state_copy_sql.py",
        "SET XACT_ABORT ON;",
        "BEGIN TRANSACTION;",
        "INSERT INTO dbo.",
        "SELECT",
        "IF @@ROWCOUNT <>",
        "COMMIT TRANSACTION;"
    )) {
        if (-not $SqlText.Contains($needle)) {
            throw "SqlPath does not look like the reviewed saved-state copy SQL artifact; missing '$needle'."
        }
    }
}

$resolvedSqlPath = Resolve-RequiredPath -Label "SqlPath" -Path $SqlPath
$resolvedSqlPlanReport = Resolve-RequiredPath -Label "SqlPlanReport" -Path $SqlPlanReport
$resolvedPlanPath = Resolve-RequiredPath -Label "PlanPath" -Path $PlanPath
$sqlText = Get-Content -Raw -LiteralPath $resolvedSqlPath -Encoding UTF8
$planReport = Get-Content -Raw -LiteralPath $resolvedSqlPlanReport -Encoding UTF8 | ConvertFrom-Json
Assert-PlanReport -PlanReport $planReport -ResolvedSqlPath $resolvedSqlPath
Assert-GeneratedSql -SqlText $sqlText

$sqlHash = (Get-FileHash -LiteralPath $resolvedSqlPath -Algorithm SHA1).Hash
$defaultReport = Join-Path (Split-Path -Parent $resolvedSqlPath) "execute-saved-state-copy-sql.json"
if (-not $Report) { $Report = $defaultReport }
if (-not $VerifyReport) { $VerifyReport = Join-Path (Split-Path -Parent $resolvedSqlPath) "saved-state-copy-verify.json" }

$reportData = [ordered]@{
    schema = "onec_saved_state_copy_sql_execution.v1"
    status = "error"
    base_url = $BaseUrl
    base_id = $ExpectedBaseId
    target_table = $ExpectedTargetTable
    source_table = $planReport.source_table
    sql_path = $resolvedSqlPath
    sql_sha1 = $sqlHash
    sql_plan_report = $resolvedSqlPlanReport
    plan_path = $resolvedPlanPath
    expected_insert_rows = [int]$planReport.expected_insert_rows
    sql_execution_attempted = $false
    sql_write_performed = $false
    verify_report = $VerifyReport
    failures = @()
}

$connectionString = "Server=$Server;Database=$Database;User ID=$User;Password=$Password;Encrypt=False;TrustServerCertificate=True;Application Name=Codex 1C Saved-State Copy Executor;"
$connection = [System.Data.SqlClient.SqlConnection]::new($connectionString)
$command = $null

try {
    $connection.Open()
    $command = $connection.CreateCommand()
    $command.CommandTimeout = $TimeoutSec
    $command.CommandText = $sqlText
    $reportData.sql_execution_attempted = $true
    [void]$command.ExecuteNonQuery()
    $reportData.sql_write_performed = $true

    $verifyArgs = @(
        "scripts/verify_1c_saved_state_copy.py",
        "--base-url",
        $BaseUrl,
        "--plan",
        $resolvedPlanPath,
        "--expected-base-id",
        $ExpectedBaseId,
        "--expected-target-table",
        $ExpectedTargetTable,
        "--timeout",
        $TimeoutSec.ToString(),
        "--report",
        $VerifyReport,
        "--require-ready",
        "--json"
    )
    $verifyOutput = & python @verifyArgs 2>&1
    $verifyExitCode = $LASTEXITCODE
    $reportData.verify_exit_code = $verifyExitCode
    $reportData.verify_output = [string]::Join("`n", @($verifyOutput))
    if ($verifyExitCode -ne 0) {
        $reportData.status = "blocked_verify_failed"
        $reportData.failures = @("SQL executed, but post-copy verification failed.")
    } else {
        $reportData.status = "verified"
    }
}
catch {
    $reportData.status = "error"
    $reportData.failures = @([string]$_)
    throw
}
finally {
    if ($null -ne $command) {
        $command.Dispose()
    }
    $connection.Close()
    $connection.Dispose()
    $reportDirectory = Split-Path -Parent $Report
    if ($reportDirectory) {
        New-Item -ItemType Directory -Force -Path $reportDirectory | Out-Null
    }
    [pscustomobject]$reportData | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $Report -Encoding UTF8
}

[pscustomobject]@{
    status = $reportData.status
    report = (Resolve-Path -LiteralPath $Report).ProviderPath
    verify_report = if (Test-Path -LiteralPath $VerifyReport) { (Resolve-Path -LiteralPath $VerifyReport).ProviderPath } else { $VerifyReport }
    sql_sha1 = $sqlHash
    expected_insert_rows = [int]$planReport.expected_insert_rows
} | ConvertTo-Json -Depth 10
