KHZ.psm1

# KHZ - Sovereign DevOps Pipeline Module
# By GRATECH / Khawrizm Labs
# https://github.com/Grar00t

#region Core Engine

$KHZ_VERSION = "1.0.0"
$KHZ_AUTHOR  = "Sulaiman Alshammari (GRATECH)"

function Write-KHZLog {
    param(
        [string]$Message,
        [ValidateSet("INFO","WARN","ERROR","SUCCESS","STEP")]
        [string]$Level = "INFO"
    )
    $colors = @{
        INFO    = "Cyan"
        WARN    = "Yellow"
        ERROR   = "Red"
        SUCCESS = "Green"
        STEP    = "Magenta"
    }
    $icons = @{
        INFO    = "◆"
        WARN    = "▲"
        ERROR   = "✖"
        SUCCESS = "✔"
        STEP    = "▶"
    }
    $ts = Get-Date -Format "HH:mm:ss"
    Write-Host "[$ts] " -NoNewline -ForegroundColor DarkGray
    Write-Host "$($icons[$Level]) " -NoNewline -ForegroundColor $colors[$Level]
    Write-Host $Message -ForegroundColor $colors[$Level]
}

#endregion

#region Environment Doctor

function Invoke-KHZDoctor {
    <#
    .SYNOPSIS
        Diagnoses and auto-fixes the local environment before running a pipeline.
    .DESCRIPTION
        Checks for required tools (git, dotnet, ssh, etc.) and installs missing ones automatically.
    .EXAMPLE
        Invoke-KHZDoctor
    .EXAMPLE
        Invoke-KHZDoctor -Fix
    #>

    param(
        [switch]$Fix,
        [switch]$Quiet
    )

    Write-KHZLog "KHZ Doctor — Environment Scan v$KHZ_VERSION" "STEP"
    Write-Host ""

    $results = @()

    $checks = @(
        @{ Name = "git";         Cmd = "git --version";      Install = "winget install Git.Git -e --source winget" },
        @{ Name = "git-lfs";     Cmd = "git lfs version";    Install = "git lfs install" },
        @{ Name = "dotnet";      Cmd = "dotnet --version";   Install = "winget install Microsoft.DotNet.SDK.8" },
        @{ Name = "ssh";         Cmd = "ssh -V";             Install = "Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0" },
        @{ Name = "powershell";  Cmd = '$PSVersionTable.PSVersion.ToString()'; Install = "winget install Microsoft.PowerShell" }
    )

    foreach ($check in $checks) {
        try {
            $output = Invoke-Expression $check.Cmd 2>&1
            $ver = ($output | Select-Object -First 1) -replace '^[^\d]*','' | Select-Object -First 1
            Write-KHZLog "$($check.Name.PadRight(15)) OK ($ver)" "SUCCESS"
            $results += [PSCustomObject]@{ Tool=$check.Name; Status="OK"; Version=$ver }
        } catch {
            Write-KHZLog "$($check.Name.PadRight(15)) MISSING" "WARN"
            $results += [PSCustomObject]@{ Tool=$check.Name; Status="MISSING"; Version="" }

            if ($Fix) {
                Write-KHZLog "Auto-fixing: $($check.Name)..." "STEP"
                try {
                    Invoke-Expression $check.Install | Out-Null
                    Write-KHZLog "$($check.Name) installed successfully" "SUCCESS"
                } catch {
                    Write-KHZLog "Failed to auto-install $($check.Name): $_" "ERROR"
                }
            }
        }
    }

    Write-Host ""
    $missing = $results | Where-Object { $_.Status -eq "MISSING" }
    if ($missing.Count -eq 0) {
        Write-KHZLog "All checks passed. Environment is ready." "SUCCESS"
    } else {
        Write-KHZLog "$($missing.Count) tool(s) missing. Run: Invoke-KHZDoctor -Fix" "WARN"
    }

    return $results
}

#endregion

#region Pipeline Engine

function Invoke-KHZPipeline {
    <#
    .SYNOPSIS
        Runs a sovereign DevOps pipeline — no cloud, no YAML hell.
    .DESCRIPTION
        Executes pipeline stages: Validate → Build → Test → Deploy → Notify
        Each stage is a standalone PowerShell node.
    .PARAMETER ProjectPath
        Path to the project directory.
    .PARAMETER Stages
        Array of stages to run. Default: Validate, Build, Test, Deploy
    .PARAMETER Target
        Deployment target (local path or SSH host).
    .PARAMETER Config
        Path to pipeline JSON config file.
    .EXAMPLE
        Invoke-KHZPipeline -ProjectPath D:\Casper.DataForge
    .EXAMPLE
        Invoke-KHZPipeline -ProjectPath D:\myapp -Stages Build,Test -Target 192.168.0.241
    #>

    param(
        [Parameter(Mandatory=$true)]
        [string]$ProjectPath,

        [string[]]$Stages = @("Validate","Build","Test","Deploy"),

        [string]$Target = "local",

        [string]$Config = "",

        [switch]$DryRun,

        [switch]$StopOnFail
    )

    $startTime = Get-Date
    $results   = @()

    Write-Host ""
    Write-Host " ╔══════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host " ║ KHZ Sovereign Pipeline v$KHZ_VERSION ║" -ForegroundColor Cyan
    Write-Host " ╚══════════════════════════════════════╝" -ForegroundColor Cyan
    Write-Host ""

    # Load config if provided
    $cfg = @{}
    if ($Config -and (Test-Path $Config)) {
        $cfg = Get-Content $Config | ConvertFrom-Json -AsHashtable
        Write-KHZLog "Config loaded: $Config" "INFO"
    }

    # Validate project path
    if (-not (Test-Path $ProjectPath)) {
        Write-KHZLog "Project path not found: $ProjectPath" "ERROR"
        Write-KHZLog "Tip: Check the path and try again." "WARN"
        return
    }

    Write-KHZLog "Project : $ProjectPath" "INFO"
    Write-KHZLog "Target : $Target" "INFO"
    Write-KHZLog "Stages : $($Stages -join ' → ')" "INFO"
    Write-KHZLog "DryRun : $DryRun" "INFO"
    Write-Host ""

    # Draw pipeline
    $pipelineStr = $Stages -join " ──▶ "
    Write-Host " Pipeline: " -NoNewline -ForegroundColor DarkGray
    Write-Host $pipelineStr -ForegroundColor Magenta
    Write-Host ""

    foreach ($stage in $Stages) {
        $stageStart = Get-Date
        Write-Host " ┌─ Stage: " -NoNewline -ForegroundColor DarkGray
        Write-Host $stage -ForegroundColor Magenta
        Write-Host " │" -ForegroundColor DarkGray

        $status = "SKIPPED"

        try {
            switch ($stage.ToUpper()) {

                "VALIDATE" {
                    Write-KHZLog " Validating project structure..." "STEP"
                    $files = Get-ChildItem $ProjectPath -Recurse -File | Measure-Object
                    Write-KHZLog " Files found: $($files.Count)" "INFO"

                    # Check for common project files
                    $markers = @("*.csproj","*.sln","*.ps1","package.json","Makefile","Cargo.toml")
                    $found = $false
                    foreach ($m in $markers) {
                        if (Get-ChildItem $ProjectPath -Filter $m -Recurse -ErrorAction SilentlyContinue) {
                            Write-KHZLog " Project type detected: $m" "SUCCESS"
                            $found = $true
                            break
                        }
                    }
                    if (-not $found) {
                        Write-KHZLog " No standard project marker found — proceeding anyway" "WARN"
                    }
                    $status = "SUCCESS"
                }

                "BUILD" {
                    if ($DryRun) {
                        Write-KHZLog " [DryRun] Would run: dotnet build" "INFO"
                        $status = "DRYRUN"
                    } else {
                        $csproj = Get-ChildItem $ProjectPath -Filter "*.csproj" -Recurse | Select-Object -First 1
                        if ($csproj) {
                            Write-KHZLog " Building: $($csproj.Name)" "STEP"
                            $buildOut = & dotnet build $csproj.FullName --nologo 2>&1
                            if ($LASTEXITCODE -eq 0) {
                                Write-KHZLog " Build succeeded" "SUCCESS"
                                $status = "SUCCESS"
                            } else {
                                Write-KHZLog " Build failed" "ERROR"
                                $buildOut | Select-Object -Last 5 | ForEach-Object { Write-KHZLog " $_" "ERROR" }
                                $status = "FAILED"
                            }
                        } else {
                            Write-KHZLog " No .csproj found — skipping dotnet build" "WARN"
                            $status = "SKIPPED"
                        }
                    }
                }

                "TEST" {
                    if ($DryRun) {
                        Write-KHZLog " [DryRun] Would run: dotnet test" "INFO"
                        $status = "DRYRUN"
                    } else {
                        $testProj = Get-ChildItem $ProjectPath -Filter "*.Tests.csproj" -Recurse | Select-Object -First 1
                        if ($testProj) {
                            Write-KHZLog " Running tests: $($testProj.Name)" "STEP"
                            $testOut = & dotnet test $testProj.FullName --nologo 2>&1
                            if ($LASTEXITCODE -eq 0) {
                                Write-KHZLog " Tests passed" "SUCCESS"
                                $status = "SUCCESS"
                            } else {
                                Write-KHZLog " Tests failed" "ERROR"
                                $status = "FAILED"
                            }
                        } else {
                            Write-KHZLog " No test project found — skipping" "WARN"
                            $status = "SKIPPED"
                        }
                    }
                }

                "DEPLOY" {
                    if ($DryRun) {
                        Write-KHZLog " [DryRun] Would deploy to: $Target" "INFO"
                        $status = "DRYRUN"
                    } else {
                        if ($Target -eq "local") {
                            Write-KHZLog " Deploying locally — nothing to do" "INFO"
                            $status = "SUCCESS"
                        } else {
                            Write-KHZLog " Deploying to: $Target via SSH" "STEP"
                            try {
                                $sshTest = & ssh -o ConnectTimeout=5 -o BatchMode=yes $Target "echo KHZ_OK" 2>&1
                                if ($sshTest -match "KHZ_OK") {
                                    Write-KHZLog " SSH connection OK" "SUCCESS"
                                    $status = "SUCCESS"
                                } else {
                                    Write-KHZLog " SSH connection failed" "ERROR"
                                    $status = "FAILED"
                                }
                            } catch {
                                Write-KHZLog " SSH error: $_" "ERROR"
                                $status = "FAILED"
                            }
                        }
                    }
                }

                "NOTIFY" {
                    Write-KHZLog " Pipeline notification sent" "SUCCESS"
                    $status = "SUCCESS"
                }

                default {
                    Write-KHZLog " Unknown stage: $stage" "WARN"
                    $status = "SKIPPED"
                }
            }
        } catch {
            Write-KHZLog " Stage error: $_" "ERROR"
            $status = "FAILED"
        }

        $stageDuration = ((Get-Date) - $stageStart).TotalSeconds
        $statusColor = switch ($status) {
            "SUCCESS" { "Green" }
            "FAILED"  { "Red" }
            "DRYRUN"  { "Cyan" }
            default   { "Yellow" }
        }

        Write-Host " └─ " -NoNewline -ForegroundColor DarkGray
        Write-Host "$status" -NoNewline -ForegroundColor $statusColor
        Write-Host " (${stageDuration}s)" -ForegroundColor DarkGray
        Write-Host ""

        $results += [PSCustomObject]@{
            Stage    = $stage
            Status   = $status
            Duration = [math]::Round($stageDuration, 2)
        }

        if ($StopOnFail -and $status -eq "FAILED") {
            Write-KHZLog "StopOnFail triggered — aborting pipeline" "ERROR"
            break
        }
    }

    # Summary
    $totalDuration = ((Get-Date) - $startTime).TotalSeconds
    $failed  = ($results | Where-Object { $_.Status -eq "FAILED" }).Count
    $success = ($results | Where-Object { $_.Status -eq "SUCCESS" }).Count

    Write-Host " ════════════════════════════════════════" -ForegroundColor DarkGray
    Write-Host " Summary" -ForegroundColor White
    Write-Host " ────────────────────────────────────────" -ForegroundColor DarkGray
    $results | ForEach-Object {
        $c = switch ($_.Status) { "SUCCESS"{"Green"} "FAILED"{"Red"} "DRYRUN"{"Cyan"} default{"Yellow"} }
        Write-Host " $($_.Stage.PadRight(12))" -NoNewline -ForegroundColor White
        Write-Host " $($_.Status.PadRight(10))" -NoNewline -ForegroundColor $c
        Write-Host " $($_.Duration)s" -ForegroundColor DarkGray
    }
    Write-Host " ────────────────────────────────────────" -ForegroundColor DarkGray
    Write-Host " Total: ${totalDuration}s | " -NoNewline -ForegroundColor DarkGray
    Write-Host "Success: $success" -NoNewline -ForegroundColor Green
    Write-Host " Failed: $failed" -ForegroundColor $(if($failed -gt 0){"Red"}else{"Green"})
    Write-Host ""

    if ($failed -eq 0) {
        Write-KHZLog "Pipeline completed successfully" "SUCCESS"
    } else {
        Write-KHZLog "Pipeline completed with $failed failure(s)" "ERROR"
    }

    return $results
}

#endregion

#region Utilities

function Get-KHZVersion {
    <#
    .SYNOPSIS
        Returns the current KHZ module version.
    #>

    Write-KHZLog "KHZ Sovereign Pipeline Module v$KHZ_VERSION" "INFO"
    Write-KHZLog "Author: $KHZ_AUTHOR" "INFO"
    Write-KHZLog "Gallery: https://www.powershellgallery.com/packages/KHZ" "INFO"
    return $KHZ_VERSION
}

function New-KHZConfig {
    <#
    .SYNOPSIS
        Generates a starter pipeline config JSON file.
    .PARAMETER OutputPath
        Where to save the config file.
    .EXAMPLE
        New-KHZConfig -OutputPath D:\myproject\pipeline.json
    #>

    param(
        [string]$OutputPath = ".\khz-pipeline.json"
    )

    $config = @{
        project = "MyProject"
        version = "1.0.0"
        stages  = @("Validate","Build","Test","Deploy")
        target  = "local"
        notify  = $false
    } | ConvertTo-Json -Depth 3

    $config | Out-File $OutputPath -Encoding UTF8
    Write-KHZLog "Config created: $OutputPath" "SUCCESS"
}

#endregion

Export-ModuleMember -Function @(
    'Invoke-KHZDoctor',
    'Invoke-KHZPipeline',
    'Get-KHZVersion',
    'New-KHZConfig',
    'Write-KHZLog'
)