# -*- coding: utf-8 -*-
<#
  说明：
  - 本脚本用于在本仓库内整理与汇总各处的 env 文件。
  - 默认不会在控制台打印任何 env 的内容，避免泄露敏感信息。

  用法示例：
    pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/env_tools.ps1 -CleanupSupabase -Merge -OutFile .env.all
#>

[CmdletBinding()]
param(
  [switch]$CleanupSupabase,
  # 兼容旧参数：-Merge 等价于 -MergeRaw
  [switch]$Merge,
  [switch]$MergeRaw,
  [switch]$MergeUnique,
  [switch]$Report,
  [switch]$ListApplied,
  # 合并优先级：默认生产优先；如需本地开发优先，传 -DevFirst
  [switch]$DevFirst,
  [switch]$AppliedOnly,
  [switch]$IncludeLegacy,
  [string]$RawOutFile = ".env.all.raw",
  [string]$OutFile = ".env.all",
  [string]$ConflictReportFile = "env.all.conflicts.md",
  [string]$AppliedReportFile = "env.applied.md"
)

$ErrorActionPreference = "Stop"

function New-Utf8NoBomEncoding {
  return New-Object System.Text.UTF8Encoding($false)
}

function Read-TextUtf8 {
  param([Parameter(Mandatory = $true)][string]$Path)
  return [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8)
}

function Write-TextUtf8NoBom {
  param(
    [Parameter(Mandatory = $true)][string]$Path,
    [Parameter(Mandatory = $true)][string]$Text
  )
  $encoding = New-Utf8NoBomEncoding
  [System.IO.File]::WriteAllText($Path, $Text, $encoding)
}

function Get-RepoRoot {
  return (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
}

function Get-EnvFiles {
  param([Parameter(Mandatory = $true)][string]$Root)

  $excludePattern = "\\(node_modules|\.git|dist|build|\.next|out|coverage|pnpm-store|artifacts)\\"
  $envFiles =
    Get-ChildItem -Path $Root -Recurse -Force -File |
    Where-Object {
      $n = $_.Name
      ($n -eq ".env" -or $n -like ".env.*" -or $n -like "*.env" -or $n -like "*.env.*" -or $n -like "*.env.example" -or $n -eq ".env.example" -or $n -like ".env.*.example") -and
      ($_.FullName -notmatch $excludePattern)
    } |
    Sort-Object FullName

  return $envFiles
}

function Get-EnvFileKind {
  param(
    [Parameter(Mandatory = $true)][string]$Root,
    [Parameter(Mandatory = $true)][string]$FullName
  )

  $rel = $FullName.Substring($Root.Length).TrimStart("\","/")
  $name = [System.IO.Path]::GetFileName($FullName)
  $lowerName = $name.ToLowerInvariant()

  if ($rel -match "^(?i)cankao[/\\\\]") { return "legacy" }
  if ($rel -match "(?i)[/\\\\]test[/\\\\]") { return "legacy" }

  if ($lowerName.EndsWith(".example")) { return "template" }
  if ($lowerName.EndsWith(".sample")) { return "template" }
  if ($lowerName.EndsWith(".mock")) { return "template" }
  if ($lowerName.EndsWith(".library")) { return "template" }
  if ($lowerName -eq "test.env") { return "template" }

  return "runtime"
}

function Get-AppliedEnvFiles {
  param([Parameter(Mandatory = $true)][string]$Root)

  # 说明：这里的[已应用]指默认启动链路/配置文件明确加载的 env 文件。
  $candidates = @(
    # 统一入口：仓库根目录的 .env.all（生产优先，且要求 Key 唯一）
    (Join-Path $Root ".env.all")
  )

  $applied = New-Object System.Collections.Generic.List[string]
  foreach ($p in ($candidates | Where-Object { Test-Path -LiteralPath $_ })) {
    $applied.Add((Resolve-Path -LiteralPath $p).Path) | Out-Null
  }

  # Docker（运行中）推断：从容器的 compose working_dir 与 compose 文件解析 env_file
  $dockerApplied = Get-AppliedEnvFilesFromDocker -Root $Root
  foreach ($p in $dockerApplied) {
    if (Test-Path -LiteralPath $p) {
      $applied.Add((Resolve-Path -LiteralPath $p).Path) | Out-Null
    }
  }

  # Vite（开发模式）默认读取：仅对仓库内已存在的 webui 目录做显式识别
  $viteDev = Join-Path $Root "LightRAG\lightrag_webui\.env.development"
  if (Test-Path -LiteralPath $viteDev) {
    $applied.Add((Resolve-Path -LiteralPath $viteDev).Path) | Out-Null
  }

  return $applied | Select-Object -Unique
}

function Get-AppliedEnvFilesFromDocker {
  param([Parameter(Mandatory = $true)][string]$Root)

  $result = New-Object System.Collections.Generic.List[string]
  $dockerOk = $false
  try {
    $null = & docker ps --format "{{.ID}}" 2>$null
    $dockerOk = $true
  } catch {
    $dockerOk = $false
  }

  if (-not $dockerOk) {
    return @()
  }

  $ids = @()
  try {
    $ids = & docker ps --format "{{.ID}}" 2>$null
  } catch {
    return @()
  }

  $composeFiles = New-Object System.Collections.Generic.HashSet[string]
  $workingDirs = New-Object System.Collections.Generic.HashSet[string]

  foreach ($id in $ids) {
    $workdir = ""
    $cfg = ""
    try { $workdir = (& docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' $id 2>$null) } catch {}
    try { $cfg = (& docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.config_files" }}' $id 2>$null) } catch {}

    if ($workdir -and $workdir -ne "<no value>" -and ($workdir -like "$Root*")) {
      $workingDirs.Add($workdir) | Out-Null
    }

    if ($cfg -and $cfg -ne "<no value>") {
      foreach ($p in ($cfg -split "[;,]" | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
        if ($p -like "$Root*") {
          $composeFiles.Add($p) | Out-Null
        }
      }
    }
  }

  foreach ($wd in $workingDirs) {
    $p = Join-Path $wd ".env"
    if (Test-Path -LiteralPath $p) {
      $result.Add($p) | Out-Null
    }
  }

  foreach ($cf in $composeFiles) {
    foreach ($p in (Get-EnvFilesFromCompose -ComposeFile $cf)) {
      if ($p -like "$Root*") {
        $result.Add($p) | Out-Null
      }
    }
  }

  return $result | Select-Object -Unique
}

function Get-EnvFilesFromCompose {
  param([Parameter(Mandatory = $true)][string]$ComposeFile)

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

  $dir = Split-Path -Parent $ComposeFile
  $lines = Get-Content -LiteralPath $ComposeFile

  $out = New-Object System.Collections.Generic.List[string]
  $inEnvFile = $false
  $envIndent = 0

  foreach ($line in $lines) {
    if ($line -match "^(\\s*)env_file\\s*:\\s*$") {
      $inEnvFile = $true
      $envIndent = $matches[1].Length
      continue
    }

    if ($inEnvFile) {
      # 退出条件：遇到更浅的缩进且不是空行/注释
      if ($line -match "^(\\s*)(\\S.*)$") {
        $indent = $matches[1].Length
        $payload = $matches[2]
        if ($indent -le $envIndent -and $payload -notmatch "^-") {
          $inEnvFile = $false
        }
      }
    }

    if (-not $inEnvFile) { continue }

    if ($line -match "^\\s*-\\s*(.+?)\\s*$") {
      $raw = $matches[1].Trim()
      $raw = $raw.Trim('"').Trim("'")
      if (-not $raw) { continue }

      $resolved = if ([System.IO.Path]::IsPathRooted($raw)) { $raw } else { Join-Path $dir $raw }
      try {
        # 归一化 .. 等相对段，避免 Test-Path / -LiteralPath 因路径包含 .. 而误判
        $resolved = [System.IO.Path]::GetFullPath($resolved)
      } catch {}

      $out.Add($resolved) | Out-Null
    }
  }

  return $out | Select-Object -Unique
}

function Remove-LinesContaining {
  param(
    [Parameter(Mandatory = $true)][string]$Path,
    [Parameter(Mandatory = $true)][string]$Pattern
  )

  $text = Read-TextUtf8 -Path $Path
  $lines = $text -split "(\r?\n)", 0

  # 上面的 split 会保留分隔符（换行）作为独立元素；便于最大限度保留原始换行风格。
  $out = New-Object System.Text.StringBuilder
  for ($i = 0; $i -lt $lines.Count; $i++) {
    $chunk = $lines[$i]
    if ($chunk -match "^\r?\n$") {
      [void]$out.Append($chunk)
      continue
    }
    if ($chunk -match $Pattern) {
      continue
    }
    [void]$out.Append($chunk)
  }

  Write-TextUtf8NoBom -Path $Path -Text $out.ToString()
}

function Get-MergeOrderScore {
  param(
    [Parameter(Mandatory = $true)][string]$Root,
    [Parameter(Mandatory = $true)][string]$FullName
  )

  $rel = $FullName.Substring($Root.Length).TrimStart("\","/")
  $name = [System.IO.Path]::GetFileName($FullName)

  # 目录组优先级：越大越[后覆盖]（优先级更高）
  $group = 0
  if ($rel -match "^(?i)wolai-backend[/\\\\]") { $group = 60 }
  elseif ($rel -match "^(?i)wolai-frontend[/\\\\]") { $group = 50 }
  elseif ($rel -match "^(?i)infra[/\\\\]convex[/\\\\]") { $group = 40 }
  elseif ($rel -match "^(?i)services[/\\\\]") { $group = 30 }
  elseif ($rel -match "^(?i)LightRAG[/\\\\]") { $group = 20 }
  elseif ($rel -match "^(?i)cankao[/\\\\]") { $group = -50 }
  elseif ($rel -match "(?i)[/\\\\]test[/\\\\]") { $group = -40 }
  else { $group = 0 }

  # 文件名优先级：越大越[后覆盖]
  $fileScore = 0
  if ($DevFirst) {
    # 开发优先：.env.local 覆盖 .env；production 文件仍保持最高
    if ($name -match "(?i)\\.production\\.local$") { $fileScore = 50 }
    elseif ($name -match "(?i)\\.production$") { $fileScore = 40 }
    elseif ($name -match "(?i)\\.local$") { $fileScore = 30 }
    elseif ($name -eq ".env") { $fileScore = 20 }
    else { $fileScore = 0 }
  } else {
    # 生产优先（默认）：.env 覆盖 .env.local；production 文件最高
    if ($name -match "(?i)\\.production\\.local$") { $fileScore = 50 }
    elseif ($name -match "(?i)\\.production$") { $fileScore = 40 }
    elseif ($name -eq ".env") { $fileScore = 30 }
    elseif ($name -match "(?i)\\.local$") { $fileScore = 20 }
    else { $fileScore = 0 }
  }

  # template/legacy 默认更弱（更靠前），避免覆盖运行时真实值
  $kind = Get-EnvFileKind -Root $Root -FullName $FullName
  $kindScore = 0
  if ($kind -eq "template") { $kindScore = -1000 }
  elseif ($kind -eq "legacy") { $kindScore = -2000 }

  return ($kindScore + ($group * 100) + $fileScore)
}

function Merge-EnvFilesRaw {
  param(
    [Parameter(Mandatory = $true)][string]$Root,
    [Parameter(Mandatory = $true)][string]$OutPath,
    [Parameter(Mandatory = $true)][System.IO.FileInfo[]]$Files
  )

  $sb = New-Object System.Text.StringBuilder
  $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  [void]$sb.AppendLine("# 自动生成（UTF-8）。此文件为[原样拼接版]，可能包含重复 Key。")
  [void]$sb.AppendLine("# 生成时间：$now")
  [void]$sb.AppendLine("# 重新生成：pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/env_tools.ps1 -MergeRaw -RawOutFile `"$($OutPath | Split-Path -Leaf)`"")

  foreach ($f in $files) {
    $rel = $f.FullName.Substring($Root.Length).TrimStart("\","/")
    [void]$sb.AppendLine("")
    [void]$sb.AppendLine("# ===== $rel =====")
    try {
      $content = Read-TextUtf8 -Path $f.FullName
    } catch {
      # 若遇到非 UTF-8 文件，仍尽量以系统默认读取（但写回统一为 UTF-8）。
      $content = [System.IO.File]::ReadAllText($f.FullName)
    }
    $content = $content.TrimEnd("`r", "`n")
    if ($content.Length -gt 0) {
      [void]$sb.AppendLine($content)
    }
    [void]$sb.AppendLine("")
  }

  Write-TextUtf8NoBom -Path $OutPath -Text $sb.ToString()
}

function Parse-EnvAssignments {
  param(
    [Parameter(Mandatory = $true)][string]$Root,
    [Parameter(Mandatory = $true)][string]$FullName
  )

  try {
    $content = Read-TextUtf8 -Path $FullName
  } catch {
    $content = [System.IO.File]::ReadAllText($FullName)
  }

  $rel = $FullName.Substring($Root.Length).TrimStart("\","/")
  $lines = $content -split "\r?\n"
  $out = @()

  for ($i = 0; $i -lt $lines.Count; $i++) {
    $line = $lines[$i]
    if ($null -eq $line) { continue }
    if ($line -match "^\s*#") { continue }
    if ($line -match "^\s*$") { continue }

    if ($line -match "^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$") {
      $key = $matches[1]
      $rhs = $matches[2]
      $out += [pscustomobject]@{
        Key = $key
        Rhs = $rhs
        Source = $rel
        Line = ($i + 1)
      }
    }
  }

  return $out
}

function Merge-EnvUnique {
  param(
    [Parameter(Mandatory = $true)][string]$Root,
    [Parameter(Mandatory = $true)][string]$OutPath,
    [Parameter(Mandatory = $true)][System.IO.FileInfo[]]$Files,
    [Parameter(Mandatory = $true)][string]$ConflictReportPath
  )

  # 排序：分数越大，越靠后覆盖
  $ordered = $Files | Sort-Object { Get-MergeOrderScore -Root $Root -FullName $_.FullName }, FullName

  $final = @{}
  $conflicts = @{}

  foreach ($f in $ordered) {
    $assignments = Parse-EnvAssignments -Root $Root -FullName $f.FullName
    foreach ($a in $assignments) {
      if ($final.ContainsKey($a.Key)) {
        if (-not $conflicts.ContainsKey($a.Key)) {
          $conflicts[$a.Key] = @($final[$a.Key].Source)
        }
        $conflicts[$a.Key] += $a.Source
      }
      $final[$a.Key] = $a
    }
  }

  $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  $sb = New-Object System.Text.StringBuilder
  [void]$sb.AppendLine("# 自动生成（UTF-8）。此文件为[去重版]：每个 Key 只保留一个定义。")
  [void]$sb.AppendLine("# 生成时间：$now")
  [void]$sb.AppendLine("# 合并规则：按目录/文件优先级排序，越靠后越覆盖（template/legacy 默认不会覆盖 runtime）。")
  [void]$sb.AppendLine("# 冲突报告：$([System.IO.Path]::GetFileName($ConflictReportPath))（不包含任何值）")
  [void]$sb.AppendLine("")

  foreach ($k in ($final.Keys | Sort-Object)) {
    $a = $final[$k]
    [void]$sb.AppendLine("# from: $($a.Source)")
    [void]$sb.AppendLine("$($a.Key)=$($a.Rhs)")
    [void]$sb.AppendLine("")
  }

  Write-TextUtf8NoBom -Path $OutPath -Text $sb.ToString()

  # 写冲突报告（不输出值）
  $rep = New-Object System.Text.StringBuilder
  [void]$rep.AppendLine("# env key 冲突报告（不含任何值）")
  [void]$rep.AppendLine("# 生成时间：$now")
  [void]$rep.AppendLine("")
  if ($conflicts.Keys.Count -eq 0) {
    [void]$rep.AppendLine("未发现重复 Key。")
  } else {
    foreach ($k in ($conflicts.Keys | Sort-Object)) {
      $chosen = $final[$k].Source
      $allSources = ($conflicts[$k] + @($chosen)) | Select-Object -Unique
      $others = $allSources | Where-Object { $_ -ne $chosen }
      [void]$rep.AppendLine("- $k")
      [void]$rep.AppendLine("  - 采用：$chosen")
      if ($others.Count -gt 0) {
        [void]$rep.AppendLine("  - 其它：$($others -join ', ')")
      }
    }
  }
  Write-TextUtf8NoBom -Path $ConflictReportPath -Text $rep.ToString()
}

$root = Get-RepoRoot

if ($Merge -and -not $MergeRaw) {
  $MergeRaw = $true
}

$appliedPaths = Get-AppliedEnvFiles -Root $root
if ($ListApplied) {
  foreach ($p in ($appliedPaths | Sort-Object)) {
    $rel = $p.Substring($root.Length).TrimStart("\","/")
    Write-Output $rel
  }
  return
}

$appliedOnlyDefault = $true
if (-not $PSBoundParameters.ContainsKey("AppliedOnly")) {
  $AppliedOnly = $appliedOnlyDefault
}

$allEnvFiles = Get-EnvFiles -Root $root

$appliedSet = @{}
foreach ($p in $appliedPaths) { $appliedSet[$p.ToLowerInvariant()] = $true }

$selected =
  if ($AppliedOnly) {
    $allEnvFiles | Where-Object { $appliedSet.ContainsKey($_.FullName.ToLowerInvariant()) }
  } else {
    $allEnvFiles
  }

if (-not $IncludeLegacy) {
  $selected =
    $selected | Where-Object {
      (Get-EnvFileKind -Root $root -FullName $_.FullName) -ne "legacy"
    }
}

$rawOutFull = Join-Path $root $RawOutFile
$outFull = Join-Path $root $OutFile
$conflictFull = Join-Path $root $ConflictReportFile
$appliedReportFull = Join-Path $root $AppliedReportFile

$root = Get-RepoRoot

if ($CleanupSupabase) {
  $filesWithSupabase =
    $allEnvFiles |
    Where-Object {
      try {
        (Read-TextUtf8 -Path $_.FullName) -match "(?i)supabase"
      } catch {
        ([System.IO.File]::ReadAllText($_.FullName)) -match "(?i)supabase"
      }
    }

  foreach ($f in $filesWithSupabase) {
    Remove-LinesContaining -Path $f.FullName -Pattern "(?i)supabase"
  }
}

if ($MergeRaw) {
  $files = $selected | Where-Object { $_.FullName -ne $rawOutFull -and $_.FullName -ne $outFull }
  Merge-EnvFilesRaw -Root $root -OutPath $rawOutFull -Files $files
}

if ($MergeUnique) {
  $files = $selected | Where-Object { $_.FullName -ne $rawOutFull -and $_.FullName -ne $outFull }
  Merge-EnvUnique -Root $root -OutPath $outFull -Files $files -ConflictReportPath $conflictFull
}

if ($Report -or $MergeRaw -or $MergeUnique) {
  # 写[已应用/未应用]报告（不包含值）
  $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  $sb2 = New-Object System.Text.StringBuilder
  [void]$sb2.AppendLine("# env 应用情况报告（不含任何值）")
  [void]$sb2.AppendLine("# 生成时间：$now")
  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("## 默认链路明确加载的 env 文件（Applied）")
  if ($appliedPaths.Count -eq 0) {
    [void]$sb2.AppendLine("- （未发现）")
  } else {
    foreach ($p in $appliedPaths) {
      $rel = $p.Substring($root.Length).TrimStart("\","/")
      [void]$sb2.AppendLine("- $rel")
    }
  }

  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("## 扫描到的 env 文件（Found）")
  $generatedEnvFull = @(
    $outFull.ToLowerInvariant(),
    $rawOutFull.ToLowerInvariant()
  )
  foreach ($f in $allEnvFiles) {
    $rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
    $kind = Get-EnvFileKind -Root $root -FullName $f.FullName
    $tag = ""
    if ($generatedEnvFull -contains $f.FullName.ToLowerInvariant()) {
      $tag = "generated"
    }
    if ($tag -ne "") {
      [void]$sb2.AppendLine("- $rel ($kind, $tag)")
    } else {
      [void]$sb2.AppendLine("- $rel ($kind)")
    }
  }

  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("## 默认链路未使用的文件（按类型分组）")

  $notApplied = $allEnvFiles | Where-Object {
    (-not $appliedSet.ContainsKey($_.FullName.ToLowerInvariant())) -and
    (-not ($generatedEnvFull -contains $_.FullName.ToLowerInvariant()))
  }

  $notAppliedRuntime = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "runtime" }
  $notAppliedTemplate = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "template" }
  $notAppliedLegacy = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "legacy" }

  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("### runtime（可能是历史遗留/需确认是否仍在用）")
  if ($notAppliedRuntime.Count -eq 0) {
    [void]$sb2.AppendLine("- （无）")
  } else {
    foreach ($f in $notAppliedRuntime) {
      $rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
      [void]$sb2.AppendLine("- $rel")
    }
  }

  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("### template（示例/样例文件）")
  if ($notAppliedTemplate.Count -eq 0) {
    [void]$sb2.AppendLine("- （无）")
  } else {
    foreach ($f in $notAppliedTemplate) {
      $rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
      [void]$sb2.AppendLine("- $rel")
    }
  }

  [void]$sb2.AppendLine("")
  [void]$sb2.AppendLine("### legacy（参考资料/测试夹带）")
  if ($notAppliedLegacy.Count -eq 0) {
    [void]$sb2.AppendLine("- （无）")
  } else {
    foreach ($f in $notAppliedLegacy) {
      $rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
      [void]$sb2.AppendLine("- $rel")
    }
  }

  Write-TextUtf8NoBom -Path $appliedReportFull -Text $sb2.ToString()
}
