# Infrastructure Validation Engine
# Main validation orchestrator for infrastructure automation

export def main [
    infra_path: string          # Path to infrastructure configuration
    --fix (-f)                  # Auto-fix issues where possible
    --report (-r): string = "md" # Report format (md|yaml|json|all)
    --output (-o): string = "./validation_results" # Output directory
    --severity: string = "warning" # Minimum severity (info|warning|error|critical)
    --ci                        # CI/CD mode (exit codes, no colors)
    --dry-run                   # Show what would be fixed without fixing
] {

    if not ($infra_path | path exists) {
        if not $ci {
            print $"🛑 Infrastructure path not found: ($infra_path)"
        }
        exit 1
    }

    let start_time = (date now)

    # Initialize validation context
    let validation_context = {
        infra_path: ($infra_path | path expand)
        output_dir: ($output | path expand)
        fix_mode: $fix
        dry_run: $dry_run
        ci_mode: $ci
        severity_filter: $severity
        report_format: $report
        start_time: $start_time
    }

    if not $ci {
        print $"🔍 Starting infrastructure validation for: ($infra_path)"
        print $"📊 Output directory: ($validation_context.output_dir)"
    }

    # Create output directory
    mkdir ($validation_context.output_dir)

    # Run validation pipeline
    let validation_results = (run_validation_pipeline $validation_context)

    # Generate reports
    let reports = (generate_reports $validation_results $validation_context)

    # Output summary
    if not $ci {
        print_validation_summary $validation_results
    }

    # Set exit code based on results
    let exit_code = (determine_exit_code $validation_results)

    if $ci {
        exit $exit_code
    }

    {
        results: $validation_results
        reports: $reports
        exit_code: $exit_code
        duration: ((date now) - $start_time)
    }
}

def run_validation_pipeline [context: record] {
    mut results = {
        summary: {
            total_checks: 0
            passed: 0
            failed: 0
            auto_fixed: 0
            skipped: 0
        }
        issues: []
        files_processed: []
        validation_context: $context
    }

    # Create rule loading context from infrastructure path
    let rule_context = {
        infra_path: $context.infra_path
        provider: (detect_provider $context.infra_path)
        taskservs: (detect_taskservs $context.infra_path)
    }

    # Load validation rules
    let rules = (load_validation_rules $rule_context)

    # Find all relevant files
    let files = (discover_infrastructure_files $context.infra_path)
    $results.files_processed = $files

    if not $context.ci_mode {
        print $"📁 Found ($files | length) files to validate"
    }

    # Run each validation rule with progress
    let total_rules = ($rules | length)
    mut rule_counter = 0

    for rule in $rules {
        $rule_counter = ($rule_counter + 1)

        if not $context.ci_mode {
            print $"🔄 [($rule_counter)/($total_rules)] Running: ($rule.name)"
        }

        let rule_results = (run_validation_rule $rule $context $files)

        if not $context.ci_mode {
            let status = if $rule_results.failed > 0 {
                $"❌ Found ($rule_results.failed) issues"
            } else {
                $"✅ Passed ($rule_results.passed) checks"
            }
            print $"   ($status)"
        }

        # Merge results
        $results.summary.total_checks = ($results.summary.total_checks + $rule_results.checks_run)
        $results.summary.passed = ($results.summary.passed + $rule_results.passed)
        $results.summary.failed = ($results.summary.failed + $rule_results.failed)
        $results.summary.auto_fixed = ($results.summary.auto_fixed + $rule_results.auto_fixed)
        $results.issues = ($results.issues | append $rule_results.issues)
    }

    $results
}

def load_validation_rules [context?: record] {
    # Import rules from rules_engine.nu
    use rules_engine.nu *
    get_all_validation_rules $context
}

def discover_infrastructure_files [infra_path: string] {
    mut files = []

    # Nickel files
    $files = ($files | append (glob $"($infra_path)/**/*.ncl"))

    # YAML files
    $files = ($files | append (glob $"($infra_path)/**/*.yaml"))
    $files = ($files | append (glob $"($infra_path)/**/*.yml"))

    # TOML files
    $files = ($files | append (glob $"($infra_path)/**/*.toml"))

    # JSON files
    $files = ($files | append (glob $"($infra_path)/**/*.json"))

    $files | flatten | uniq | sort
}

def run_validation_rule [rule: record, context: record, files: list] {
    mut rule_results = {
        rule_id: $rule.id
        checks_run: 0
        passed: 0
        failed: 0
        auto_fixed: 0
        issues: []
    }

    # Filter files by rule pattern
    let target_files = ($files | where {|file|
        $file =~ $rule.files_pattern
    })

    for file in $target_files {
        $rule_results.checks_run = ($rule_results.checks_run + 1)

        if not $context.ci_mode and ($target_files | length) > 10 {
            let progress = ($rule_results.checks_run * 100 / ($target_files | length))
            print $"     Processing... ($progress)% (($rule_results.checks_run)/($target_files | length))"
        }

        let file_result = (run_file_validation $rule $file $context)

        if $file_result.passed {
            $rule_results.passed = ($rule_results.passed + 1)
        } else {
            $rule_results.failed = ($rule_results.failed + 1)

            mut issue_to_add = $file_result.issue

            # Try auto-fix if enabled and possible
            if $context.fix_mode and $rule.auto_fix and (not $context.dry_run) {
                if not $context.ci_mode {
                    print $"     🔧 Auto-fixing: ($file | path basename)"
                }
                let fix_result = (attempt_auto_fix $rule $issue_to_add $context)
                if $fix_result.success {
                    $rule_results.auto_fixed = ($rule_results.auto_fixed + 1)
                    $issue_to_add = ($issue_to_add | upsert auto_fixed true)
                    if not $context.ci_mode {
                        print $"     ✅ Fixed: ($fix_result.message)"
                    }
                }
            }

            $rule_results.issues = ($rule_results.issues | append $issue_to_add)
        }
    }

    $rule_results
}

def run_file_validation [rule: record, file: string, context: record] {
    # Use the config-driven rule execution system
    use rules_engine.nu *
    execute_rule $rule $file $context
}

def attempt_auto_fix [rule: record, issue: record, context: record] {
    # Use the config-driven fix execution system
    use rules_engine.nu *
    execute_fix $rule $issue $context
}

def generate_reports [results: record, context: record] {
    use report_generator.nu *

    mut reports = {}

    if $context.report_format == "all" or $context.report_format == "md" {
        let md_report = (generate_markdown_report $results $context)
        $md_report | save ($context.output_dir | path join "validation_report.md")
        $reports.markdown = ($context.output_dir | path join "validation_report.md")
    }

    if $context.report_format == "all" or $context.report_format == "yaml" {
        let yaml_report = (generate_yaml_report $results $context)
        $yaml_report | save ($context.output_dir | path join "validation_results.yaml")
        $reports.yaml = ($context.output_dir | path join "validation_results.yaml")
    }

    if $context.report_format == "all" or $context.report_format == "json" {
        let json_report = (generate_json_report $results $context)
        $json_report | save ($context.output_dir | path join "validation_results.json")
        $reports.json = ($context.output_dir | path join "validation_results.json")
    }

    $reports
}

def print_validation_summary [results: record] {
    let summary = $results.summary
    let critical_count = ($results.issues | where severity == "critical" | length)
    let error_count = ($results.issues | where severity == "error" | length)
    let warning_count = ($results.issues | where severity == "warning" | length)

    print ""
    print "📋 Validation Summary"
    print "===================="
    print $"✅ Passed: ($summary.passed)/($summary.total_checks)"

    if $critical_count > 0 {
        print $"🚨 Critical: ($critical_count)"
    }
    if $error_count > 0 {
        print $"❌ Errors: ($error_count)"
    }
    if $warning_count > 0 {
        print $"⚠️  Warnings: ($warning_count)"
    }
    if $summary.auto_fixed > 0 {
        print $"🔧 Auto-fixed: ($summary.auto_fixed)"
    }

    print ""
}

def determine_exit_code [results: record] {
    let critical_count = ($results.issues | where severity == "critical" | length)
    let error_count = ($results.issues | where severity == "error" | length)
    let warning_count = ($results.issues | where severity == "warning" | length)

    if $critical_count > 0 {
        1  # Critical errors
    } else if $error_count > 0 {
        2  # Non-critical errors
    } else if $warning_count > 0 {
        3  # Only warnings
    } else {
        0  # All good
    }
}

def detect_provider [infra_path: string] {
    # Try to detect provider from file structure or configuration
    let nickel_files = (glob ($infra_path | path join "**/*.ncl"))

    for file in $decl_files {
        let content = (open $file --raw)
        if ($content | str contains "upcloud") {
            return "upcloud"
        } else if ($content | str contains "aws") {
            return "aws"
        } else if ($content | str contains "gcp") {
            return "gcp"
        }
    }

    # Check directory structure for provider hints
    if (($infra_path | path join "upcloud") | path exists) {
        return "upcloud"
    } else if (($infra_path | path join "aws") | path exists) {
        return "aws"
    } else if (($infra_path | path join "local") | path exists) {
        return "local"
    }

    "unknown"
}

def detect_taskservs [infra_path: string] {
    mut taskservs = []

    let nickel_files = (glob ($infra_path | path join "**/*.ncl"))
    let yaml_files = (glob ($infra_path | path join "**/*.yaml"))

    let all_files = ($decl_files | append $yaml_files)

    for file in $all_files {
        let content = (open $file --raw)

        if ($content | str contains "kubernetes") {
            $taskservs = ($taskservs | append "kubernetes")
        }
        if ($content | str contains "containerd") {
            $taskservs = ($taskservs | append "containerd")
        }
        if ($content | str contains "cilium") {
            $taskservs = ($taskservs | append "cilium")
        }
        if ($content | str contains "rook") {
            $taskservs = ($taskservs | append "rook")
        }
    }

    $taskservs | uniq
}
