#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT

BeforeAll {
    $script:ScriptPath = Join-Path $PSScriptRoot '../../evals/Build-AgentBehaviorSpec.ps1'

    Import-Module powershell-yaml -ErrorAction Stop

    function script:Invoke-Generator {
        param(
            [Parameter(Mandatory)] [string]$Root,
            [switch]$DryRun,
            [switch]$Force
        )
        $argList = @('-NoProfile', '-NoLogo', '-File', $script:ScriptPath, '-RepoRoot', $Root)
        if ($DryRun) { $argList += '-WhatIf' }
        if ($Force) { $argList += '-Force' }
        $stdout = & pwsh @argList 2>&1
        return [pscustomobject]@{
            ExitCode = $LASTEXITCODE
            Output = ($stdout | Out-String)
        }
    }

    function script:Initialize-FixtureRoot {
        param([Parameter(Mandatory)] [string]$Root)
        New-Item -ItemType Directory -Path $Root -Force | Out-Null
        New-Item -ItemType Directory -Path (Join-Path $Root 'evals/agent-behavior/stimuli') -Force | Out-Null
    }

    function script:Write-Partial {
        param(
            [Parameter(Mandatory)] [string]$Root,
            [Parameter(Mandatory)] [string]$Slug,
            [Parameter(Mandatory)] [string]$Content
        )
        $path = Join-Path $Root "evals/agent-behavior/stimuli/$Slug.yml"
        [System.IO.File]::WriteAllText($path, $Content)
        return $path
    }

    function script:Write-SeedEvalYaml {
        param(
            [Parameter(Mandatory)] [string]$Root,
            [Parameter(Mandatory)] [string]$Content
        )
        $path = Join-Path $Root 'evals/agent-behavior/eval.yaml'
        [System.IO.File]::WriteAllText($path, $Content)
        return $path
    }

    function script:Read-OutputYaml {
        param([Parameter(Mandatory)] [string]$Root)
        $path = Join-Path $Root 'evals/agent-behavior/eval.yaml'
        return [System.IO.File]::ReadAllText($path)
    }

    function script:Read-OutputObject {
        param([Parameter(Mandatory)] [string]$Root)
        return ConvertFrom-Yaml -Yaml (script:Read-OutputYaml -Root $Root)
    }
}

Describe 'Build-AgentBehaviorSpec.ps1' -Tag 'Unit' {
    BeforeEach {
        $script:TestRoot = Join-Path $TestDrive ([Guid]::NewGuid().ToString())
        Initialize-FixtureRoot -Root $script:TestRoot
    }

    Context 'Rendering with multiple partials' {
        It 'Concatenates partials in alphabetical order and injects agent tag from slug' {
            Write-Partial -Root $script:TestRoot -Slug 'beta' -Content @"
stimuli:
  - name: beta-case
    prompt: Beta agent prompt.
    graders:
      - type: output-matches
        name: beta-grader
        config:
          pattern: "(?i)beta"
"@
            Write-Partial -Root $script:TestRoot -Slug 'alpha' -Content @"
stimuli:
  - name: alpha-case
    prompt: Alpha agent prompt.
    graders:
      - type: output-matches
        name: alpha-grader
        config:
          pattern: "(?i)alpha"
"@

            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Be 0

            $spec = Read-OutputObject -Root $script:TestRoot
            $spec.stimuli | Should -HaveCount 2
            $spec.stimuli[0].name | Should -Be 'alpha-case'
            $spec.stimuli[1].name | Should -Be 'beta-case'
            $spec.stimuli[0].tags.agent | Should -Be 'alpha'
            $spec.stimuli[1].tags.agent | Should -Be 'beta'
        }

        It 'Writes the generator banner as the first line' {
            Write-Partial -Root $script:TestRoot -Slug 'solo' -Content @"
stimuli:
  - name: solo-case
    prompt: Solo agent prompt.
"@
            (Invoke-Generator -Root $script:TestRoot).ExitCode | Should -Be 0
            $text = Read-OutputYaml -Root $script:TestRoot
            $firstLine = ($text -split "`n")[0]
            $firstLine | Should -Be '# Generated by Build-AgentBehaviorSpec.ps1 - do not edit by hand.'
        }
    }

    Context 'No partials' {
        It 'Emits an empty stimuli list and exits 0' {
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Be 0
            $text = Read-OutputYaml -Root $script:TestRoot
            $text | Should -Match '(?m)^stimuli:\s*\[\]\s*$'
        }
    }

    Context 'Top-level key preservation' {
        It 'Preserves byte-identical top-level keys from the existing eval.yaml prelude' {
            $seed = @"
# Generated by Build-AgentBehaviorSpec.ps1 - do not edit by hand.
suite: agent-behavior-test
version: 1
description: >
  Multi-line
  description block.
config:
  executor: copilot-sdk
  runs: 3
stimuli: []
"@
            Write-SeedEvalYaml -Root $script:TestRoot -Content $seed
            Write-Partial -Root $script:TestRoot -Slug 'gamma' -Content @"
stimuli:
  - name: gamma-case
    prompt: Gamma agent prompt.
"@

            $result = Invoke-Generator -Root $script:TestRoot -Force
            $result.ExitCode | Should -Be 0

            $regenerated = Read-OutputYaml -Root $script:TestRoot
            $seedLines = ($seed -replace "`r`n", "`n") -split "`n"
            $newLines = ($regenerated -replace "`r`n", "`n") -split "`n"
            for ($i = 0; $i -lt 8; $i++) {
                $newLines[$i] | Should -Be $seedLines[$i]
            }
        }
    }

    Context 'Tag injection conflict' {
        It 'Halts when a partial declares tags.agent that disagrees with the file slug' {
            Write-Partial -Root $script:TestRoot -Slug 'expected-slug' -Content @"
stimuli:
  - name: mismatched
    prompt: A prompt.
    tags:
      agent: other-slug
"@
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Not -Be 0
            $result.Output | Should -Match "expected-slug"
            $result.Output | Should -Match "other-slug"
        }

        It 'Accepts a partial that explicitly tags the matching agent slug' {
            Write-Partial -Root $script:TestRoot -Slug 'matched-slug' -Content @"
stimuli:
  - name: matched
    prompt: A prompt.
    tags:
      agent: matched-slug
      category: agent-behavior
"@
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Be 0
            $spec = Read-OutputObject -Root $script:TestRoot
            $spec.stimuli[0].tags.agent | Should -Be 'matched-slug'
            $spec.stimuli[0].tags.category | Should -Be 'agent-behavior'
        }
    }

    Context 'Drift detection (-WhatIf)' {
        It 'Exits 0 when on-disk output already matches the rendered spec' {
            Write-Partial -Root $script:TestRoot -Slug 'driftless' -Content @"
stimuli:
  - name: driftless-case
    prompt: Driftless prompt.
"@
            (Invoke-Generator -Root $script:TestRoot).ExitCode | Should -Be 0

            $result = Invoke-Generator -Root $script:TestRoot -DryRun
            $result.ExitCode | Should -Be 0
            $diffPath = Join-Path $script:TestRoot 'logs/agent-behavior-spec-drift.diff'
            Test-Path -LiteralPath $diffPath | Should -BeFalse
        }

        It 'Exits 1 and writes a drift diff when on-disk content differs' {
            Write-Partial -Root $script:TestRoot -Slug 'drift' -Content @"
stimuli:
  - name: drift-case
    prompt: Drift prompt.
"@
            (Invoke-Generator -Root $script:TestRoot).ExitCode | Should -Be 0

            Write-Partial -Root $script:TestRoot -Slug 'drift' -Content @"
stimuli:
  - name: drift-case
    prompt: Drift prompt UPDATED.
"@
            $result = Invoke-Generator -Root $script:TestRoot -DryRun
            $result.ExitCode | Should -Be 1

            $diffPath = Join-Path $script:TestRoot 'logs/agent-behavior-spec-drift.diff'
            Test-Path -LiteralPath $diffPath | Should -BeTrue
            $diff = [System.IO.File]::ReadAllText($diffPath)
            $diff | Should -Match 'expected'
            $diff | Should -Match 'actual'
        }
    }

    Context 'Overwrite semantics' {
        It 'Refuses to overwrite an existing file that differs without -Force' {
            Write-SeedEvalYaml -Root $script:TestRoot -Content "stimuli: []`n"
            Write-Partial -Root $script:TestRoot -Slug 'agent-a' -Content @"
stimuli:
  - name: agent-a-case
    prompt: Prompt.
"@
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Not -Be 0
            $result.Output | Should -Match 'Force'
        }

        It 'Overwrites the existing file with -Force' {
            Write-SeedEvalYaml -Root $script:TestRoot -Content "stimuli: []`n"
            Write-Partial -Root $script:TestRoot -Slug 'agent-b' -Content @"
stimuli:
  - name: agent-b-case
    prompt: Prompt.
"@
            $result = Invoke-Generator -Root $script:TestRoot -Force
            $result.ExitCode | Should -Be 0
            $spec = Read-OutputObject -Root $script:TestRoot
            $spec.stimuli[0].name | Should -Be 'agent-b-case'
        }

        It 'Skips writing when -Force is set but content is identical' {
            Write-Partial -Root $script:TestRoot -Slug 'idem' -Content @"
stimuli:
  - name: idem-case
    prompt: Prompt.
"@
            (Invoke-Generator -Root $script:TestRoot).ExitCode | Should -Be 0
            $first = Read-OutputYaml -Root $script:TestRoot
            (Invoke-Generator -Root $script:TestRoot -Force).ExitCode | Should -Be 0
            $second = Read-OutputYaml -Root $script:TestRoot
            $second | Should -Be $first
        }
    }

    Context 'Idempotency' {
        It 'Produces the same output when run twice in a row' {
            Write-Partial -Root $script:TestRoot -Slug 'idem' -Content @"
stimuli:
  - name: idem-case
    prompt: Prompt.
"@
            (Invoke-Generator -Root $script:TestRoot).ExitCode | Should -Be 0
            $first = Read-OutputYaml -Root $script:TestRoot
            $drift = Invoke-Generator -Root $script:TestRoot -DryRun
            $drift.ExitCode | Should -Be 0
            $second = Read-OutputYaml -Root $script:TestRoot
            $second | Should -Be $first
        }
    }

    Context 'Partial validation errors' {
        It 'Names the offending file when a partial is invalid YAML' {
            Write-Partial -Root $script:TestRoot -Slug 'broken' -Content "stimuli:`n  - name: x`n   bad-indent:"
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Not -Be 0
            $result.Output | Should -Match 'broken\.yml'
        }

        It 'Fails when a stimulus is missing the name field' {
            Write-Partial -Root $script:TestRoot -Slug 'no-name' -Content @"
stimuli:
  - prompt: A prompt with no name.
"@
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Not -Be 0
            $result.Output | Should -Match "name"
        }

        It 'Fails when a stimulus is missing the prompt field' {
            Write-Partial -Root $script:TestRoot -Slug 'no-prompt' -Content @"
stimuli:
  - name: prompt-less
"@
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Not -Be 0
            $result.Output | Should -Match "prompt"
        }

        It 'Silently skips a partial whose stimuli list is empty' {
            Write-Partial -Root $script:TestRoot -Slug 'silent' -Content "stimuli: []`n"
            $result = Invoke-Generator -Root $script:TestRoot
            $result.ExitCode | Should -Be 0
            $spec = Read-OutputObject -Root $script:TestRoot
            ($null -eq $spec.stimuli -or $spec.stimuli.Count -eq 0) | Should -BeTrue
        }
    }
}
