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]$SummaryPath,
    [string]$OutputPath
)

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

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 $SummaryPath) { throw "SummaryPath is required." }
if (-not $OutputPath) { throw "OutputPath is required." }

$metadataFields = @(
    "attributes", "tabular_sections", "dimensions", "resources", "forms",
    "templates", "commands", "addressing_attributes", "accounting_flags",
    "columns", "enum_values", "integration_service_channels", "operations",
    "url_templates"
    "tabular_section_attributes"
)

$summary = Get-Content -LiteralPath $SummaryPath -Raw -Encoding UTF8 | ConvertFrom-Json
$checks = New-Object System.Collections.Generic.List[object]
$tableChecks = New-Object System.Collections.Generic.List[object]

foreach ($row in $summary.outputs) {
    $report = Get-Content -LiteralPath $row.output -Raw -Encoding UTF8 | ConvertFrom-Json
    $tables = @($report.object_storage_routes | Where-Object { $_.route_kind -eq "table" -and $_.physical_name_candidate } | ForEach-Object { $_.physical_name_candidate })
    foreach ($field in $metadataFields) {
        if (-not ($report.PSObject.Properties.Name -contains $field)) { continue }
        foreach ($item in @($report.$field)) {
            $itemCandidateTables = $tables
            if ($item.PSObject.Properties.Name -contains "parent_physical_tables") {
                $parentTables = @($item.parent_physical_tables | Where-Object { $_.PSObject.Properties.Name -contains "table" -and $_.table } | ForEach-Object { $_.table })
                if ($parentTables.Count -gt 0) {
                    $itemCandidateTables = $parentTables
                }
            }
            foreach ($column in @($item.physical_columns | Where-Object { $_.PSObject.Properties.Name -contains "column" -and $_.column })) {
                $checks.Add([pscustomobject]@{
                    kind = $report.kind
                    object_name = $report.identity.name
                    object_guid = $report.identity.guid
                    metadata_field = $field
                    item_name = $item.name
                    item_guid = $item.uuid
                    value_type = if ($column.PSObject.Properties.Name -contains "value_type") { $column.value_type } elseif ($column.PSObject.Properties.Name -contains "value_types") { ($column.value_types -join ",") } else { $null }
                    predicted_column = $column.column
                    candidate_tables = $itemCandidateTables
                })
            }
            $physicalTables = @()
            if ($item.PSObject.Properties.Name -contains "physical_tables") {
                $physicalTables = @($item.physical_tables)
            }
            foreach ($tableCandidate in @($physicalTables | Where-Object { $_.PSObject.Properties.Name -contains "table" -and $_.table })) {
                $tableChecks.Add([pscustomobject]@{
                    kind = $report.kind
                    object_name = $report.identity.name
                    object_guid = $report.identity.guid
                    metadata_field = $field
                    item_name = $item.name
                    item_guid = $item.uuid
                    predicted_table = $tableCandidate.table
                    reason = $tableCandidate.reason
                })
            }
        }
    }
}

$connectionString = "Server=$Server;Database=$Database;User ID=$User;Password=$Password;Encrypt=False;TrustServerCertificate=True;Application Name=Codex 1C Column Prediction Validation;"
$connection = [System.Data.SqlClient.SqlConnection]::new($connectionString)

$results = New-Object System.Collections.Generic.List[object]
$foundCount = 0

try {
    $connection.Open()
    $allTables = @(
        @($checks | ForEach-Object { $_.candidate_tables } | Where-Object { $_ })
        @($tableChecks | ForEach-Object { $_.predicted_table } | Where-Object { $_ })
    ) | Select-Object -Unique
    $schemaLookup = @{}
    if ($allTables.Count -gt 0) {
        $command = $connection.CreateCommand()
        $command.CommandTimeout = 0
        $tableParams = @()
        for ($i = 0; $i -lt $allTables.Count; $i++) {
            $name = "@t$i"
            $tableParams += $name
            $null = $command.Parameters.Add($name, [System.Data.SqlDbType]::NVarChar, 128)
            $command.Parameters[$name].Value = $allTables[$i]
        }
        $command.CommandText = @"
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN ($($tableParams -join ","))
"@
        $reader = $command.ExecuteReader()
        try {
            while ($reader.Read()) {
                $item = [pscustomobject]@{
                    table = [string]$reader.GetValue(0)
                    column = [string]$reader.GetValue(1)
                    data_type = [string]$reader.GetValue(2)
                    max_length = if ($reader.IsDBNull(3)) { $null } else { $reader.GetValue(3) }
                    precision = if ($reader.IsDBNull(4)) { $null } else { $reader.GetValue(4) }
                    scale = if ($reader.IsDBNull(5)) { $null } else { $reader.GetValue(5) }
                    nullable = [string]$reader.GetValue(6)
                }
                $key = "$($item.table)|$($item.column)"
                if (-not $schemaLookup.ContainsKey($key)) {
                    $schemaLookup[$key] = New-Object System.Collections.Generic.List[object]
                }
                $schemaLookup[$key].Add($item)
            }
        }
        finally {
            if ($reader -ne $null) {
                $reader.Close()
                $reader.Dispose()
            }
            if ($command -ne $null) { $command.Dispose() }
        }
    }

    foreach ($check in $checks) {
        $matches = New-Object System.Collections.Generic.List[object]
        foreach ($table in @($check.candidate_tables)) {
            $key = "$table|$($check.predicted_column)"
            if ($schemaLookup.ContainsKey($key)) {
                foreach ($match in $schemaLookup[$key]) {
                    $matches.Add($match)
                }
            }
        }
        if ($matches.Count -gt 0) { $foundCount++ }
        $results.Add([pscustomobject]@{
            kind = $check.kind
            object_name = $check.object_name
            object_guid = $check.object_guid
            metadata_field = $check.metadata_field
            item_name = $check.item_name
            item_guid = $check.item_guid
            value_type = $check.value_type
            predicted_column = $check.predicted_column
            candidate_tables = $check.candidate_tables
            found = $matches.Count -gt 0
            matches = $matches
        })
    }
}
finally {
    if ($connection -ne $null) { $connection.Dispose() }
}

$byKind = $results | Group-Object kind | ForEach-Object {
    [pscustomobject]@{
        kind = $_.Name
        predicted_count = $_.Count
        found_count = @($_.Group | Where-Object found).Count
    }
}

$tableResults = New-Object System.Collections.Generic.List[object]
$tableFoundCount = 0
foreach ($check in $tableChecks) {
    $exists = $false
    foreach ($key in $schemaLookup.Keys) {
        if ($key.StartsWith("$($check.predicted_table)|")) {
            $exists = $true
            break
        }
    }
    if ($exists) { $tableFoundCount++ }
    $tableResults.Add([pscustomobject]@{
        kind = $check.kind
        object_name = $check.object_name
        object_guid = $check.object_guid
        metadata_field = $check.metadata_field
        item_name = $check.item_name
        item_guid = $check.item_guid
        predicted_table = $check.predicted_table
        reason = $check.reason
        found = $exists
    })
}

$tableByKind = $tableResults | Group-Object kind | ForEach-Object {
    [pscustomobject]@{
        kind = $_.Name
        predicted_count = $_.Count
        found_count = @($_.Group | Where-Object found).Count
    }
}

$report = [pscustomobject]@{
    schema = "onec_predicted_column_validation.v1"
    summary = $SummaryPath
    database = $Database
    predicted_count = $checks.Count
    found_count = $foundCount
    predicted_table_count = $tableChecks.Count
    found_table_count = $tableFoundCount
    by_kind = $byKind
    table_by_kind = $tableByKind
    results = $results
    table_results = $tableResults
}

$resolvedOutput = [System.IO.Path]::GetFullPath($OutputPath)
New-Item -ItemType Directory -Force -Path ([System.IO.Path]::GetDirectoryName($resolvedOutput)) | Out-Null
$report | ConvertTo-Json -Depth 12 | Out-File -LiteralPath $resolvedOutput -Encoding UTF8
$report | Select-Object schema, predicted_count, found_count, predicted_table_count, found_table_count | ConvertTo-Json -Compress
