# ----------------------------------------------------------------
# Scenario 2: Code Quality Gatekeeper
# Flow: PR submit -> AI Review -> Check CI -> Auto-merge if pass
#
# Commands chained:
#   1. pr +list              -- list open PRs
#   2. pr +view              -- get PR details
#   3. pr +files             -- get changed files
#   4. pr +diff              -- get diff content
#   5. gitlink-code-review   -- AI code review (claude CLI or keyword fallback)
#   6. api POST .../reviews  -- post review comment
#   7. ci +builds            -- check CI status
#   8. pr +merge             -- auto-merge if quality threshold met
# ----------------------------------------------------------------
#Requires -Version 5.1

param(
    [string]$Owner = "",
    [string]$Repo = "",
    [string]$PrId = "",
    [int]$Threshold = 80,
    [switch]$DryRun,
    [switch]$Help
)

$ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force

if ($Help) {
    Write-Host "Usage: powershell 02-code-quality-gatekeeper.ps1 -Owner OWNER -Repo REPO [-PrId ID] [-Threshold SCORE] [-DryRun]"
    Write-Host ""
    Write-Host "  -Owner OWNER        Repository owner"
    Write-Host "  -Repo  REPO         Repository name"
    Write-Host "  -PrId  ID           Specific PR to review (default: all open PRs)"
    Write-Host "  -Threshold SCORE    Min quality score to auto-merge (default: 80)"
    Write-Host "  -DryRun             Preview actions without executing"
    exit 0
}

Check-Auth
$r = Resolve-OwnerRepo $Owner $Repo
$Owner = $r.Owner; $Repo = $r.Repo

# ----------------------------------------------------------------
# Default fallback strings (defined at script scope to avoid indented here-string issue)
$DefaultSkillDimensions = "1. 代码质量: 复杂度、命名、注释、格式`n2. 安全性: SQL注入、XSS、敏感信息、认证、输入验证`n3. 性能: 循环效率、资源泄漏、N+1查询、内存`n4. 可维护性: 代码重复、职责单一、依赖耦合、测试覆盖"

$DefaultReviewPrompt = @'
你是代码审查专家。请按 gitlink-code-review skill 的审查维度分析以下 PR。

## 审查维度与检查项

{SKILL_DIMENSIONS}

## 评分标准
- 90-100: 优秀，可直接合并
- 75-89: 良好，建议合并
- 60-74: 一般，需要改进
- <60: 较差，不建议合并

## 问题严重级别
- CRITICAL: 阻止合并
- HIGH: 强烈建议修复
- MEDIUM: 建议修复
- LOW: 可选修复

## PR 数据

PR 标题: {PR_TITLE}
变更文件:
{FILE_LIST}
代码差异:
{DIFF_CONTENT}

## 输出要求

请严格按以下 JSON 格式输出，不要输出其他内容:
{"total": <0-100>, "quality": <0-25>, "security": <0-25>, "performance": <0-25>, "maintainability": <0-25>, "issues": [{"severity": "HIGH/MEDIUM/LOW", "category": "quality/security/performance/maintainability", "file": "文件路径", "rule": "规则名", "description": "问题描述", "suggestion": "修复建议"}], "positive_notes": [{"description": "优秀实践描述"}], "recommendations": ["改进建议1"], "verdict": "PASS或FAIL"}
'@

function Review-PR {
    param([string]$PrId)

    Log-Title "Reviewing PR #$PrId"

    # -- Step 1: Get PR details --
    Log-Step "Fetching PR details..."
    $prJson = Invoke-GLCheck pr,+view,--owner,$Owner,--repo,$Repo,--id,$PrId
    if (-not $prJson) {
        Log-Warn "Failed to fetch PR #$PrId, skipping"
        return
    }
    $prData = $prJson.data
    $prTitle = if ($prData.title) { $prData.title }
               elseif ($prData.subject) { $prData.subject }
               elseif ($prData.issue.subject) { $prData.issue.subject }
               else { "N/A" }
    $prState = if ($prData.state) { $prData.state }
               elseif ($prData.status) { $prData.status }
               else { "N/A" }
    $prAuthor = if ($prData.author.login) { $prData.author.login }
                elseif ($prData.author.username) { $prData.author.username }
                elseif ($prData.issue.author.login) { $prData.issue.author.login }
                else { "N/A" }
    Log-Ok "PR #${PrId}: `"$prTitle`" by @$prAuthor (state: $prState)"

    # -- Step 2: Get changed files --
    Log-Step "Fetching changed files..."
    $filesJson = Invoke-GL pr,+files,--owner,$Owner,--repo,$Repo,--id,$PrId
    $fileNames = @()
    $fileCount = 0
    if ($filesJson) {
        try {
            $filesData = $filesJson | ConvertFrom-Json
            if ($filesData.data.files) {
                $fileNames = @($filesData.data.files | ForEach-Object {
                    if ($_.name) { $_.name } elseif ($_.filename) { $_.filename } else { "unknown" }
                })
                $fileCount = $fileNames.Count
            }
        } catch { }
    }
    Log-Ok "Changed files: $fileCount"
    foreach ($f in $fileNames) { Write-Host "    $f" }

    # -- Step 3: Get diff --
    Log-Step "Fetching diff..."
    $diffContent = ""
    $diffLines = 0
    $diffJson = Invoke-GL pr,+diff,--owner,$Owner,--repo,$Repo,--id,$PrId
    if ($diffJson) {
        try {
            $diffData = $diffJson | ConvertFrom-Json
            $diffLines_arr = @()
            if ($diffData.data.files) {
                foreach ($f in $diffData.data.files) {
                    if ($f.sections) {
                        foreach ($s in $f.sections) {
                            if ($s.lines) {
                                foreach ($l in $s.lines) {
                                    if ($l.content) { $diffLines_arr += $l.content }
                                }
                            }
                        }
                    }
                }
            }
            $diffContent = ($diffLines_arr -join "`n")
            if ($diffContent.Length -gt 5000) { $diffContent = $diffContent.Substring(0, 5000) }
            $diffLines = $diffLines_arr.Count
        } catch { }
    }
    Log-Ok "Diff: $diffLines lines"

    # -- Step 4: AI-powered code review --
    Log-Step "AI analyzing code quality..."

    $fileListText = ""
    foreach ($f in $fileNames) { $fileListText += "- $f`n" }

    $diffTruncated = $diffContent
    if ($diffTruncated.Length -gt 4000) { $diffTruncated = $diffTruncated.Substring(0, 4000) }

    # Load skill dimensions from SKILL.md
    $skillDir = "$PSScriptRoot/../skills/gitlink-code-review"
    $skillDimensions = ""
    if (Test-Path "$skillDir/SKILL.md") {
        $skillMd = Get-Content "$skillDir/SKILL.md" -Raw -Encoding UTF8
        if ($skillMd -match '(?s)## 📊 审查维度(.+?)## 🔧 使用方式') {
            $section = $Matches[1]
            $dimLines = ($section -split "`n" | Where-Object { $_ -match '^- \*\*' } | Select-Object -First 20)
            $skillDimensions = $dimLines -join "`n"
        }
    }

    if (-not $skillDimensions) {
        $skillDimensions = $DefaultSkillDimensions
    }

    $reviewPrompt = $DefaultReviewPrompt -replace '\{PR_TITLE\}', $prTitle -replace '\{FILE_LIST\}', $fileListText -replace '\{DIFF_CONTENT\}', $diffTruncated -replace '\{SKILL_DIMENSIONS\}', $skillDimensions

    $aiAvailable = $false
    $totalScore = 0
    $scoreQuality = 25
    $scoreSecurity = 25
    $scorePerformance = 25
    $scoreMaintainability = 25
    $issuesFound = @()
    $aiPositive = @()
    $aiRecommendations = @()
    $aiVerdict = "PASS"

    $claudePath = (Get-Command claude -ErrorAction SilentlyContinue).Source
    if ($claudePath) {
        Log-Info "Calling AI agent for code review (may take 30-60s)..."

        try {
            $promptFile = [System.IO.Path]::GetTempFileName()
            $outFile = [System.IO.Path]::GetTempFileName()
            [System.IO.File]::WriteAllText($promptFile, $reviewPrompt, [System.Text.Encoding]::UTF8)

            $aiResult = $null
            $proc = Start-Process -FilePath $claudePath `
                -ArgumentList @("-p", "--output-format", "json") `
                -RedirectStandardInput $promptFile `
                -RedirectStandardOutput $outFile `
                -NoNewWindow -Wait -PassThru

            if ($proc.ExitCode -eq 0 -and (Test-Path $outFile)) {
                $aiOutput = [System.IO.File]::ReadAllText($outFile, [System.Text.Encoding]::UTF8)
                try {
                    $aiOutputJson = $aiOutput | ConvertFrom-Json
                    $aiResult = $aiOutputJson.result
                } catch {
                    $aiResult = $aiOutput
                }
            }

            Remove-Item $promptFile -Force -ErrorAction SilentlyContinue
            Remove-Item $outFile -Force -ErrorAction SilentlyContinue

            if ($aiResult) {
                # Extract JSON from AI response (may contain markdown wrapping)
                $aiJson = $null
                $jsonCandidate = Extract-JsonBlock $aiResult
                if ($jsonCandidate) {
                    try {
                        $testJson = $jsonCandidate | ConvertFrom-Json
                        if ($testJson.total -ne $null -and $testJson.verdict) {
                            $aiJson = $testJson
                        }
                    } catch { }
                }
                # Fallback: try direct parse
                if (-not $aiJson) {
                    try {
                        $testJson = $aiResult | ConvertFrom-Json
                        if ($testJson.total -ne $null -and $testJson.verdict) {
                            $aiJson = $testJson
                        }
                    } catch { }
                }

                if ($aiJson) {
                    $totalScore = [int]($aiJson.total -as [int])
                    $scoreQuality = [int]($aiJson.quality -as [int])
                    $scoreSecurity = [int]($aiJson.security -as [int])
                    $scorePerformance = [int]($aiJson.performance -as [int])
                    $scoreMaintainability = [int]($aiJson.maintainability -as [int])
                    $aiVerdict = if ($aiJson.verdict) { $aiJson.verdict } else { "PASS" }

                    if ($aiJson.issues) {
                        foreach ($issue in $aiJson.issues) {
                            if ($issue -is [string]) {
                                $issuesFound += $issue
                            } else {
                                $sev = if ($issue.severity) { $issue.severity } else { "?" }
                                $cat = if ($issue.category) { $issue.category } else { "?" }
                                $desc = if ($issue.description) { $issue.description }
                                        elseif ($issue.rule) { $issue.rule } else { "unknown" }
                                $file = if ($issue.file) { " ($($issue.file))" } else { "" }
                                $sug = if ($issue.suggestion) { " -> $($issue.suggestion)" } else { "" }
                                $issuesFound += "[$sev] ${cat}: $desc${file}${sug}"
                            }
                        }
                    }
                    if ($aiJson.positive_notes) {
                        foreach ($note in $aiJson.positive_notes) {
                            if ($note.description) { $aiPositive += $note.description }
                            elseif ($note -is [string]) { $aiPositive += $note }
                        }
                    }
                    if ($aiJson.recommendations) {
                        foreach ($rec in $aiJson.recommendations) {
                            if ($rec -is [string]) { $aiRecommendations += $rec }
                        }
                    }

                    $aiAvailable = $true
                    Log-Ok "AI review complete (verdict: $aiVerdict)"
                } else {
                    Log-Warn "Could not parse AI response JSON, falling back to keyword-based"
                }
            }
        } catch {
            Log-Warn "AI call failed: $_"
        }
    }

    # -- Fallback: keyword-based heuristics --
    if (-not $aiAvailable) {
        Log-Warn "AI not available, falling back to keyword-based analysis"

        $scoreQuality = 25
        $scoreSecurity = 25
        $scorePerformance = 25
        $scoreMaintainability = 25
        $issuesFound = @()

        if ($diffContent -match '(?i)password|secret|token|api_key|apikey|private_key') {
            $scoreSecurity -= 15
            $issuesFound += "SECURITY: 检测到可能的硬编码凭证"
        }
        if ($diffContent -match '(?i)eval\(|exec\(|system\(|shell_exec|os\.system|subprocess\.call') {
            $scoreSecurity -= 10
            $issuesFound += "SECURITY: 检测到危险函数调用"
        }
        if ($diffContent -match '(?i)TODO|FIXME|HACK|XXX') {
            $scoreQuality -= 5
            $issuesFound += "QUALITY: 存在 TODO/FIXME/HACK 注释"
        }
        if ($diffContent -match '(?i)SELECT \*|\.findAll\(\)|\.all\(\)') {
            $scorePerformance -= 10
            $issuesFound += "PERFORMANCE: 可能的全表查询"
        }
        if ($diffContent -match '(?i)sleep\(|time\.sleep|Thread\.sleep') {
            $scorePerformance -= 5
            $issuesFound += "PERFORMANCE: 检测到阻塞式 sleep"
        }
        if ($fileCount -gt 20) {
            $scoreMaintainability -= 10
            $issuesFound += "MAINTAINABILITY: 变更文件数量过多 ($fileCount)"
        }

        $totalScore = [Math]::Max(0, $scoreQuality + $scoreSecurity + $scorePerformance + $scoreMaintainability)
    }

    # -- Print review report --
    Divider
    if ($aiAvailable) {
        Log-Info "AI Review Report for PR #$PrId"
    } else {
        Log-Info "Review Report for PR #$PrId (keyword-based)"
    }
    Write-Host ""
    Write-Host "  Overall Score:        $totalScore / 100"
    Write-Host "  Code Quality:         $scoreQuality / 25"
    Write-Host "  Security:             $scoreSecurity / 25"
    Write-Host "  Performance:          $scorePerformance / 25"
    Write-Host "  Maintainability:      $scoreMaintainability / 25"
    Write-Host ""

    if ($issuesFound.Count -gt 0) {
        Write-Host "  Issues Found:"
        foreach ($issue in $issuesFound) {
            Write-Host "    - $issue"
        }
        Write-Host ""
    }

    if ($aiPositive.Count -gt 0) {
        Write-Host "  Positive Notes:"
        foreach ($note in $aiPositive) {
            Write-Host "    + $note"
        }
        Write-Host ""
    }

    if ($aiRecommendations.Count -gt 0) {
        Write-Host "  Recommendations:"
        foreach ($rec in $aiRecommendations) {
            Write-Host "    > $rec"
        }
        Write-Host ""
    }

    # -- Step 5: Post review comment --
    if ($aiAvailable) {
        $reviewHeader = "## AI Code Quality Review - PR #$PrId"
    } else {
        $reviewHeader = "## Code Quality Review - PR #$PrId (keyword-based)"
    }

    $reviewBody = "$reviewHeader`n`n### Scores`n"
    $reviewBody += "| Dimension | Score | Max |`n"
    $reviewBody += "|-----------|-------|-----|`n"
    $reviewBody += "| Code Quality | $scoreQuality | 25 |`n"
    $reviewBody += "| Security | $scoreSecurity | 25 |`n"
    $reviewBody += "| Performance | $scorePerformance | 25 |`n"
    $reviewBody += "| Maintainability | $scoreMaintainability | 25 |`n"
    $reviewBody += "| **Total** | **$totalScore** | **100** |`n`n"
    $reviewBody += "### Issues Found`n"

    if ($issuesFound.Count -gt 0) {
        foreach ($issue in $issuesFound) {
            $reviewBody += "- $issue`n"
        }
    } else {
        $reviewBody += "No issues found.`n"
    }

    if ($aiPositive.Count -gt 0) {
        $reviewBody += "`n### Positive Notes`n"
        foreach ($note in $aiPositive) {
            $reviewBody += "- $note`n"
        }
    }

    if ($aiRecommendations.Count -gt 0) {
        $reviewBody += "`n### Recommendations`n"
        foreach ($rec in $aiRecommendations) {
            $reviewBody += "- $rec`n"
        }
    }

    $verdictText = if ($totalScore -ge $Threshold) {
        "**PASS** - Score $totalScore >= threshold $Threshold. Ready to merge."
    } else {
        "**FAIL** - Score $totalScore < threshold $Threshold. Please address the issues above."
    }
    $reviewBody += "`n### Verdict`n${verdictText}`n`n---`n*Auto-reviewed by gitlink-cli code-quality-gatekeeper workflow (skill: gitlink-code-review)*"

    Log-Step "Posting review comment..."
    $reviewEvent = if ($totalScore -ge $Threshold) { "APPROVE" } else { "COMMENT" }
    $reviewPayload = @{
        body  = $reviewBody
        event = $reviewEvent
    } | ConvertTo-Json -Compress

    $reviewResult = Invoke-GL api,POST,"/$Owner/$Repo/pulls/$PrId/reviews",--body,$reviewPayload
    if ($reviewResult) {
        try {
            $reviewOk = (($reviewResult | ConvertFrom-Json).ok -eq $true)
        } catch { $reviewOk = $false }
        if ($reviewOk) {
            Log-Ok "Review posted"
        } else {
            Log-Warn "Review post may have failed (review API might not be available)"
        }
    } else {
        Log-Warn "Review post may have failed"
    }

    # -- Step 6: Check CI --
    Log-Step "Checking CI build status..."
    $ciPresent = $false
    $ciPassed = $true
    $ciJson = Invoke-GL ci,+builds,--owner,$Owner,--repo,$Repo
    if ($ciJson) {
        try {
            $ciData = $ciJson | ConvertFrom-Json
            $builds = @()
            if ($ciData.data.builds) { $builds = @($ciData.data.builds) }
            elseif ($ciData.data -is [array]) { $builds = @($ciData.data) }
            if ($builds.Count -gt 0) {
                $ciPresent = $true
                foreach ($b in $builds) {
                    $status = if ($b.status) { $b.status } elseif ($b.state) { $b.state } else { "unknown" }
                    $name = if ($b.name) { $b.name } else { "build" }
                    if ($status -notin @("success", "passed", "completed")) {
                        $ciPassed = $false
                        Log-Warn "CI '$name' status: $status"
                    } else {
                        Log-Ok "CI '$name' status: $status"
                    }
                }
            }
        } catch { }
    }
    if (-not $ciPresent) { Log-Info "No CI builds found" }

    # -- Step 7: Auto-merge --
    if ($totalScore -ge $Threshold -and $ciPassed) {
        Log-Step "Quality score $totalScore >= $Threshold and CI passed"
        if ($DryRun) {
            Log-Warn "[DRY RUN] Would auto-merge PR #$PrId"
        } else {
            Log-Step "Auto-merging PR #$PrId..."
            $mergeResult = Invoke-GL pr,+merge,--owner,$Owner,--repo,$Repo,--id,$PrId,--method,merge
            if ($mergeResult) {
                try {
                    $mergeOk = (($mergeResult | ConvertFrom-Json).ok -eq $true)
                } catch { $mergeOk = $false }
                if ($mergeOk) {
                    Log-Ok "PR #$PrId merged successfully!"
                } else {
                    Log-Err "Auto-merge failed"
                }
            } else {
                Log-Err "Auto-merge failed"
            }
        }
    } else {
        Log-Warn "PR #$PrId not auto-merged (score: $totalScore, threshold: $Threshold, CI passed: $ciPassed)"
    }

    Write-Host ""
}

# ----------------------------------------------------------------
function Extract-JsonBlock {
    param([string]$Text)
    # Find JSON by balanced brace matching
    $depth = 0
    $start = -1
    $results = @()
    for ($i = 0; $i -lt $Text.Length; $i++) {
        $c = $Text[$i]
        if ($c -eq '{') {
            if ($depth -eq 0) { $start = $i }
            $depth++
        } elseif ($c -eq '}') {
            $depth--
            if ($depth -eq 0 -and $start -ge 0) {
                $results += $Text.Substring($start, $i - $start + 1)
                $start = -1
            }
        }
    }
    # Return the last valid JSON object (usually the most complete)
    for ($i = $results.Count - 1; $i -ge 0; $i--) {
        try {
            $obj = $results[$i] | ConvertFrom-Json
            if ($obj.total -ne $null -and $obj.verdict) {
                return $results[$i]
            }
        } catch { }
    }
    return $null
}

# ================================================================
# Main
# ================================================================
Log-Title "Code Quality Gatekeeper"

if ($PrId) {
    Review-PR $PrId
} else {
    Log-Step "Fetching open PRs..."
    $prsResult = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,50
    $prList = @()
    if ($prsResult) {
        try {
            $prsJson = $prsResult | ConvertFrom-Json
            $prData = $prsJson.data
            if ($prData.issues) { $prList = @($prData.issues) }
            elseif ($prData.pulls) { $prList = @($prData.pulls) }
            elseif ($prData -is [array]) { $prList = $prData }
        } catch { }
    }
    $prCount = $prList.Count
    Log-Ok "Found $prCount open PRs"

    if ($prCount -eq 0) {
        Log-Info "No open PRs to review"
        exit 0
    }

    $reviewed = 0
    $passed = 0
    $failed = 0

    foreach ($pr in $prList) {
        $prNum = if ($pr.pull_request_number) { $pr.pull_request_number }
                 elseif ($pr.number) { $pr.number }
                 elseif ($pr.id) { $pr.id }
                 else { $null }
        if (-not $prNum) { continue }
        Review-PR $prNum
        $reviewed++
    }

    Log-Title "Gatekeeper Summary"
    Write-Host "  PRs Reviewed: $reviewed" -ForegroundColor Green
    Write-Host "  Threshold:    $Threshold" -ForegroundColor Green
}
