#!/usr/bin/env nu

# Package validation tool - validates package integrity and completeness
#
# Validates:
# - Package structure and required files
# - Binary execution and dependencies
# - Configuration completeness
# - Installation script functionality
# - Security and integrity checks

use std log

def main [
    --package-path: string                      # Path to package file or directory to validate
    --validation-type: string = "complete"     # Validation type: quick, complete, security
    --temp-workspace: string = ""              # Temporary workspace (auto-generated if empty)
    --cleanup                                   # Cleanup workspace after validation
    --skip-execution: bool = false             # Skip binary execution tests
    --verbose                                   # Enable verbose logging
    --output-format: string = "text"           # Output format: text, json, table
]
{

    let package_root = ($package_path | path expand)
    let temp_workspace = if $temp_workspace == "" {
        $env.TMPDIR | path join $"package-validation-(random uuid)"
    } else {
        $temp_workspace | path expand
    }

    let validation_config = {
        package_path: $package_root
        validation_type: $validation_type
        temp_workspace: $temp_workspace
        cleanup: $cleanup
        skip_execution: $skip_execution
        verbose: $verbose
        output_format: $output_format
    }

    log info $"Starting package validation with config: ($validation_config)"

    # Validate package exists
    if not ($package_root | path exists) {
        log error $"Package does not exist: ($package_root)"
        exit 1
    }

    # Create temporary workspace
    mkdir ($validation_config.temp_workspace)

    let validation_results = []

    try {
        # Extract/prepare package for validation
        let preparation_result = prepare_package_for_validation $validation_config

        if $preparation_result.status != "success" {
            log error $"Failed to prepare package for validation: ($preparation_result.reason)"
            exit 1
        }

        let package_dir = $preparation_result.extracted_path

        # Run validation tests based on type
        let validation_categories = match $validation_config.validation_type {
            "quick" => ["structure", "files"]
            "complete" => ["structure", "files", "binaries", "configuration", "installation"]
            "security" => ["structure", "files", "binaries", "configuration", "installation", "security"]
            _ => ["structure", "files"]
        }

        for category in $validation_categories {
            let category_result = match $category {
                "structure" => { validate_package_structure $package_dir $validation_config }
                "files" => { validate_required_files $package_dir $validation_config }
                "binaries" => { validate_binaries $package_dir $validation_config }
                "configuration" => { validate_configuration $package_dir $validation_config }
                "installation" => { validate_installation_scripts $package_dir $validation_config }
                "security" => { validate_security $package_dir $validation_config }
                _ => {
                    log warning $"Unknown validation category: ($category)"
                    { category: $category, status: "skipped", reason: "unknown category" }
                }
            }
            let validation_results = ($validation_results | append $category_result)
        }

        # Generate validation report
        let validation_report = generate_validation_report $validation_results $validation_config

        # Cleanup if requested
        if $validation_config.cleanup {
            rm -rf ($validation_config.temp_workspace)
            log info "Cleaned up validation workspace"
        }

        let summary = {
            total_categories: ($validation_results | length)
            passed_categories: ($validation_results | where status == "passed" | length)
            failed_categories: ($validation_results | where status == "failed" | length)
            warnings: ($validation_results | get warnings | flatten | length)
            overall_status: (if ($validation_results | where status == "failed" | length) > 0 { "failed" } else { "passed" })
            validation_config: $validation_config
            results: $validation_results
            report: $validation_report
        }

        # Output results in requested format
        output_validation_results $summary $validation_config

        if $summary.overall_status == "failed" {
            log error $"Package validation failed - ($summary.failed_categories) categories failed"
            exit 1
        } else {
            log info $"Package validation passed - all ($summary.passed_categories) categories validated successfully"
        }

        return $summary

    } catch {|err|
        # Ensure cleanup even on error
        if $validation_config.cleanup and ($validation_config.temp_workspace | path exists) {
            rm -rf ($validation_config.temp_workspace)
        }

        log error $"Package validation failed: ($err.msg)"
        exit 1
    }
}

# Prepare package for validation (extract if needed)
def prepare_package_for_validation [validation_config: record]
{
    let package_path = $validation_config.package_path

    # Determine if package is an archive or directory
    if ($package_path | path type) == "dir" {
        # Package is already a directory
        return {
            status: "success"
            extracted_path: $package_path
            extraction_needed: false
        }
    }

    # Package is an archive, need to extract
    let extraction_dir = ($validation_config.temp_workspace | path join "extracted")
    mkdir $extraction_dir

    try {
        let package_name = ($package_path | path basename)

        if ($package_name | str ends-with ".tar.gz") or ($package_name | str ends-with ".tgz") {
            # Extract tar.gz
            cd $extraction_dir
            tar -xzf $package_path
        } else if ($package_name | str ends-with ".zip") {
            # Extract zip
            cd $extraction_dir
            unzip $package_path
        } else {
            return {
                status: "failed"
                reason: $"unsupported package format: ($package_name)"
            }
        }

        # Find the extracted directory (usually there's a single top-level directory)
        let extracted_contents = (ls $extraction_dir)
        let extracted_path = if ($extracted_contents | length) == 1 and (($extracted_contents | get 0.type) == "dir") {
            ($extracted_contents | get 0.name)
        } else {
            $extraction_dir
        }

        return {
            status: "success"
            extracted_path: $extracted_path
            extraction_needed: true
        }

    } catch {|err|
        return {
            status: "failed"
            reason: $"extraction failed: ($err.msg)"
        }
    }
}

# Validate package structure
def validate_package_structure [
    package_dir: string
    validation_config: record
]
{
    log info "Validating package structure..."

    let start_time = (date now)
    let structure_issues = []
    let structure_warnings = []

    # Define expected structure for provisioning packages
    let expected_structure = [
        { path: "platform", type: "dir", required: true, description: "Platform binaries directory" },
        { path: "core", type: "dir", required: true, description: "Core libraries directory" },
        { path: "config", type: "dir", required: true, description: "Configuration directory" },
        { path: "README.md", type: "file", required: false, description: "Package documentation" },
        { path: "install.sh", type: "file", required: false, description: "Installation script (Unix)" },
        { path: "install.bat", type: "file", required: false, description: "Installation script (Windows)" }
    ]

    # Check each expected component
    for component in $expected_structure {
        let component_path = ($package_dir | path join $component.path)
        let exists = ($component_path | path exists)
        let correct_type = if $exists {
            ($component_path | path type) == $component.type
        } else {
            false
        }

        if $component.required and not $exists {
            $structure_issues = ($structure_issues | append {
                type: "missing_required"
                path: $component.path
                description: $component.description
                severity: "error"
            })
        } else if $exists and not $correct_type {
            $structure_issues = ($structure_issues | append {
                type: "incorrect_type"
                path: $component.path
                expected: $component.type
                actual: ($component_path | path type)
                severity: "error"
            })
        } else if not $component.required and not $exists {
            $structure_warnings = ($structure_warnings | append {
                type: "missing_optional"
                path: $component.path
                description: $component.description
                severity: "warning"
            })
        }
    }

    # Check for unexpected files at root level
    let root_files = (ls $package_dir | get name | each { path basename })
    let expected_files = ($expected_structure | get path)
    let unexpected_files = ($root_files | where {|file| not ($file in $expected_files) })

    for unexpected in $unexpected_files {
        $structure_warnings = ($structure_warnings | append {
            type: "unexpected_file"
            path: $unexpected
            severity: "info"
        })
    }

    let status = if ($structure_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "structure"
        status: $status
        issues: $structure_issues
        warnings: $structure_warnings
        checks_performed: ($expected_structure | length)
        duration: ((date now) - $start_time)
    }
}

# Validate required files
def validate_required_files [
    package_dir: string
    validation_config: record
]
{
    log info "Validating required files..."

    let start_time = (date now)
    let file_issues = []
    let file_warnings = []

    # Check platform binaries
    let platform_dir = ($package_dir | path join "platform")
    if ($platform_dir | path exists) {
        let binaries = (find $platform_dir -type f -executable)
        if ($binaries | length) == 0 {
            $file_issues = ($file_issues | append {
                type: "no_executables"
                path: "platform/"
                description: "No executable binaries found in platform directory"
                severity: "error"
            })
        }

        # Check for common binaries
        let expected_binaries = ["provisioning-orchestrator", "control-center"]
        for binary in $expected_binaries {
            let binary_files = ($binaries | where ($it =~ $binary))
            if ($binary_files | length) == 0 {
                $file_warnings = ($file_warnings | append {
                    type: "missing_binary"
                    path: $"platform/($binary)"
                    description: $"Expected binary not found: ($binary)"
                    severity: "warning"
                })
            }
        }
    }

    # Check core libraries
    let core_dir = ($package_dir | path join "core")
    if ($core_dir | path exists) {
        let essential_core_paths = ["lib", "bin/provisioning"]
        for core_path in $essential_core_paths {
            let full_path = ($core_dir | path join $core_path)
            if not ($full_path | path exists) {
                $file_issues = ($file_issues | append {
                    type: "missing_core_component"
                    path: $"core/($core_path)"
                    description: $"Essential core component missing: ($core_path)"
                    severity: "error"
                })
            }
        }
    }

    # Check configuration files
    let config_dir = ($package_dir | path join "config")
    if ($config_dir | path exists) {
        let config_files = (find $config_dir -name "*.toml" -type f)
        if ($config_files | length) == 0 {
            $file_warnings = ($file_warnings | append {
                type: "no_config_files"
                path: "config/"
                description: "No TOML configuration files found"
                severity: "warning"
            })
        }
    }

    # Validate file permissions
    let permission_issues = validate_file_permissions $package_dir $validation_config
    $file_issues = ($file_issues | append $permission_issues.issues)
    $file_warnings = ($file_warnings | append $permission_issues.warnings)

    let status = if ($file_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "files"
        status: $status
        issues: $file_issues
        warnings: $file_warnings
        checks_performed: 4
        duration: ((date now) - $start_time)
    }
}

# Validate file permissions
def validate_file_permissions [
    package_dir: string
    validation_config: record
]
{
    let issues = []
    let warnings = []

    # Check that executables are actually executable
    let platform_dir = ($package_dir | path join "platform")
    if ($platform_dir | path exists) {
        let files = (ls $platform_dir | where type == file)
        for file in $files {
            let is_executable = try {
                # Check if file has execute permissions
                let perms = (ls -l $file.name | get 0.permissions)
                $perms =~ "x"
            } catch {
                false
            }

            if not $is_executable {
                $issues = ($issues | append {
                    type: "not_executable"
                    path: ($file.name | str replace $package_dir "")
                    description: "Binary file is not executable"
                    severity: "error"
                })
            }
        }
    }

    return { issues: $issues, warnings: $warnings }
}

# Validate binaries
def validate_binaries [
    package_dir: string
    validation_config: record
]
{
    log info "Validating binaries..."

    let start_time = (date now)
    let binary_issues = []
    let binary_warnings = []

    let platform_dir = ($package_dir | path join "platform")
    if not ($platform_dir | path exists) {
        return {
            category: "binaries"
            status: "skipped"
            reason: "no platform directory found"
            issues: []
            warnings: []
            duration: ((date now) - $start_time)
        }
    }

    let binaries = (find $platform_dir -type f -executable)

    for binary in $binaries {
        let binary_result = validate_single_binary $binary $validation_config

        if $binary_result.status == "failed" {
            $binary_issues = ($binary_issues | append $binary_result.issues)
        }

        if ($binary_result.warnings | length) > 0 {
            $binary_warnings = ($binary_warnings | append $binary_result.warnings)
        }
    }

    let status = if ($binary_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "binaries"
        status: $status
        issues: $binary_issues
        warnings: $binary_warnings
        binaries_tested: ($binaries | length)
        checks_performed: ($binaries | length)
        duration: ((date now) - $start_time)
    }
}

# Validate a single binary
def validate_single_binary [
    binary_path: string
    validation_config: record
]
{
    let binary_name = ($binary_path | path basename)
    let issues = []
    let warnings = []

    if $validation_config.verbose {
        log info $"Validating binary: ($binary_name)"
    }

    # Check binary format
    try {
        let file_info = (file $binary_path)

        if not (($file_info =~ "ELF") or ($file_info =~ "Mach-O") or ($file_info =~ "PE32")) {
            $issues = ($issues | append {
                type: "invalid_binary_format"
                binary: $binary_name
                description: $"Binary format not recognized: ($file_info)"
                severity: "error"
            })
        }

        # Check if binary is stripped (for release builds)
        if not ($file_info =~ "stripped") {
            $warnings = ($warnings | append {
                type: "not_stripped"
                binary: $binary_name
                description: "Binary contains debug symbols (not stripped)"
                severity: "info"
            })
        }

    } catch {|err|
        $issues = ($issues | append {
            type: "file_analysis_failed"
            binary: $binary_name
            description: $"Failed to analyze binary: ($err.msg)"
            severity: "error"
        })
    }

    # Test binary execution (if not skipped)
    if not $validation_config.skip_execution {
        let execution_result = test_binary_execution $binary_path $validation_config

        if $execution_result.status == "failed" {
            $issues = ($issues | append $execution_result.issues)
        }

        if ($execution_result.warnings | length) > 0 {
            $warnings = ($warnings | append $execution_result.warnings)
        }
    }

    let status = if ($issues | length) > 0 { "failed" } else { "passed" }

    return {
        status: $status
        binary: $binary_name
        issues: $issues
        warnings: $warnings
    }
}

# Test binary execution
def test_binary_execution [
    binary_path: string
    validation_config: record
]
{
    let binary_name = ($binary_path | path basename)
    let issues = []
    let warnings = []

    # Test basic execution (--help or --version)
    let help_commands = ["--help", "-h", "--version", "-V"]
    let execution_success = false

    for cmd in $help_commands {
        try {
            let result = (run-external --redirect-combine $binary_path $cmd | complete)
            if $result.exit_code == 0 {
                $execution_success = true
                break
            }
        } catch {
            # Continue to next command
        }
    }

    if not $execution_success {
        $issues = ($issues | append {
            type: "execution_failed"
            binary: $binary_name
            description: "Binary does not respond to common help commands"
            severity: "error"
        })
    }

    # Check for missing dynamic dependencies (Linux/macOS)
    try {
        let ldd_result = match $nu.os-info.name {
            "linux" => { run-external --redirect-combine "ldd" $binary_path | complete }
            "macos" => { run-external --redirect-combine "otool" "-L" $binary_path | complete }
            _ => { { exit_code: 1, stdout: "", stderr: "" } }
        }

        if $ldd_result.exit_code == 0 and ($ldd_result.stdout =~ "not found") {
            $warnings = ($warnings | append {
                type: "missing_dependencies"
                binary: $binary_name
                description: "Binary has missing dynamic dependencies"
                severity: "warning"
            })
        }
    } catch {
        # Dependency checking not available or failed
    }

    let status = if ($issues | length) > 0 { "failed" } else { "passed" }

    return {
        status: $status
        issues: $issues
        warnings: $warnings
    }
}

# Validate configuration
def validate_configuration [
    package_dir: string
    validation_config: record
]
{
    log info "Validating configuration..."

    let start_time = (date now)
    let config_issues = []
    let config_warnings = []

    let config_dir = ($package_dir | path join "config")
    if not ($config_dir | path exists) {
        return {
            category: "configuration"
            status: "skipped"
            reason: "no config directory found"
            issues: []
            warnings: []
            duration: ((date now) - $start_time)
        }
    }

    # Validate TOML files
    let toml_files = (find $config_dir -name "*.toml" -type f)

    for toml_file in $toml_files {
        let toml_result = validate_toml_file $toml_file $validation_config

        if $toml_result.status == "failed" {
            $config_issues = ($config_issues | append $toml_result.issues)
        }

        if ($toml_result.warnings | length) > 0 {
            $config_warnings = ($config_warnings | append $toml_result.warnings)
        }
    }

    let status = if ($config_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "configuration"
        status: $status
        issues: $config_issues
        warnings: $config_warnings
        config_files_tested: ($toml_files | length)
        checks_performed: ($toml_files | length)
        duration: ((date now) - $start_time)
    }
}

# Validate TOML file
def validate_toml_file [
    toml_file: string
    validation_config: record
]
{
    let file_name = ($toml_file | path basename)
    let issues = []
    let warnings = []

    try {
        # Try to parse TOML file
        let toml_content = (open $toml_file)

        # Check for essential sections (if it's a main config file)
        if ($file_name =~ "config") {
            let expected_sections = ["paths", "providers", "general"]
            for section in $expected_sections {
                if not ($section in ($toml_content | columns)) {
                    $warnings = ($warnings | append {
                        type: "missing_config_section"
                        file: $file_name
                        section: $section
                        description: $"Configuration section missing: ($section)"
                        severity: "warning"
                    })
                }
            }
        }

    } catch {|err|
        $issues = ($issues | append {
            type: "toml_parse_error"
            file: $file_name
            description: $"Failed to parse TOML file: ($err.msg)"
            severity: "error"
        })
    }

    let status = if ($issues | length) > 0 { "failed" } else { "passed" }

    return {
        status: $status
        file: $file_name
        issues: $issues
        warnings: $warnings
    }
}

# Validate installation scripts
def validate_installation_scripts [
    package_dir: string
    validation_config: record
]
{
    log info "Validating installation scripts..."

    let start_time = (date now)
    let script_issues = []
    let script_warnings = []

    # Check for installation scripts
    let install_scripts = [
        { name: "install.sh", platform: "unix" },
        { name: "install.bat", platform: "windows" }
    ]

    let found_scripts = 0

    for script in $install_scripts {
        let script_path = ($package_dir | path join $script.name)

        if ($script_path | path exists) {
            $found_scripts = $found_scripts + 1
            let script_result = validate_install_script $script_path $script.platform $validation_config

            if $script_result.status == "failed" {
                $script_issues = ($script_issues | append $script_result.issues)
            }

            if ($script_result.warnings | length) > 0 {
                $script_warnings = ($script_warnings | append $script_result.warnings)
            }
        }
    }

    if $found_scripts == 0 {
        $script_warnings = ($script_warnings | append {
            type: "no_install_scripts"
            description: "No installation scripts found"
            severity: "warning"
        })
    }

    let status = if ($script_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "installation"
        status: $status
        issues: $script_issues
        warnings: $script_warnings
        scripts_found: $found_scripts
        checks_performed: $found_scripts
        duration: ((date now) - $start_time)
    }
}

# Validate install script
def validate_install_script [
    script_path: string
    platform: string
    validation_config: record
]
{
    let script_name = ($script_path | path basename)
    let issues = []
    let warnings = []

    # Check script is executable
    let is_executable = try {
        let perms = (ls -l $script_path | get 0.permissions)
        $perms =~ "x"
    } catch {
        false
    }

    if not $is_executable and $platform == "unix" {
        $issues = ($issues | append {
            type: "script_not_executable"
            script: $script_name
            description: "Install script is not executable"
            severity: "error"
        })
    }

    # Basic syntax check
    try {
        let content = (open $script_path --raw)

        if $platform == "unix" {
            # Check for shebang
            if not ($content | str starts-with "#!") {
                $warnings = ($warnings | append {
                    type: "missing_shebang"
                    script: $script_name
                    description: "Script missing shebang line"
                    severity: "warning"
                })
            }
        }

        # Check for dangerous commands
        let dangerous_patterns = ["rm -rf /", "sudo rm -rf", "format c:"]
        for pattern in $dangerous_patterns {
            if ($content =~ $pattern) {
                $issues = ($issues | append {
                    type: "dangerous_command"
                    script: $script_name
                    pattern: $pattern
                    description: $"Script contains potentially dangerous command: ($pattern)"
                    severity: "error"
                })
            }
        }

    } catch {|err|
        $issues = ($issues | append {
            type: "script_read_error"
            script: $script_name
            description: $"Failed to read script: ($err.msg)"
            severity: "error"
        })
    }

    let status = if ($issues | length) > 0 { "failed" } else { "passed" }

    return {
        status: $status
        script: $script_name
        issues: $issues
        warnings: $warnings
    }
}

# Validate security aspects
def validate_security [
    package_dir: string
    validation_config: record
]
{
    log info "Validating security..."

    let start_time = (date now)
    let security_issues = []
    let security_warnings = []

    # Check for potentially unsafe files
    let unsafe_patterns = ["*.tmp", "*.log", "*password*", "*secret*", "*.key", "*.pem"]
    for pattern in $unsafe_patterns {
        let found_files = (find $package_dir -name $pattern -type f)
        for file in $found_files {
            $security_warnings = ($security_warnings | append {
                type: "potentially_unsafe_file"
                path: ($file | str replace $package_dir "")
                description: $"File may contain sensitive information: ($file | path basename)"
                severity: "warning"
            })
        }
    }

    # Check file permissions for security
    let world_writable = (find $package_dir -perm -o+w -type f)
    for file in $world_writable {
        $security_issues = ($security_issues | append {
            type: "world_writable_file"
            path: ($file | str replace $package_dir "")
            description: "File is world-writable"
            severity: "error"
        })
    }

    let status = if ($security_issues | length) > 0 { "failed" } else { "passed" }

    {
        category: "security"
        status: $status
        issues: $security_issues
        warnings: $security_warnings
        checks_performed: 2
        duration: ((date now) - $start_time)
    }
}

# Generate validation report
def generate_validation_report [
    validation_results: list
    validation_config: record
]
{
    let total_issues = ($validation_results | get issues | flatten | length)
    let total_warnings = ($validation_results | get warnings | flatten | length)

    let report = {
        timestamp: (date now)
        package_path: $validation_config.package_path
        validation_type: $validation_config.validation_type
        overall_status: (if ($validation_results | where status == "failed" | length) > 0 { "failed" } else { "passed" })
        summary: {
            total_categories: ($validation_results | length)
            passed: ($validation_results | where status == "passed" | length)
            failed: ($validation_results | where status == "failed" | length)
            skipped: ($validation_results | where status == "skipped" | length)
            total_issues: $total_issues
            total_warnings: $total_warnings
        }
        categories: $validation_results
        all_issues: ($validation_results | get issues | flatten)
        all_warnings: ($validation_results | get warnings | flatten)
    }

    # Save report to file
    let report_file = ($validation_config.temp_workspace | path join "validation-report.json")
    $report | to json | save $report_file
    log info $"Validation report saved to: ($report_file)"

    return $report
}

# Output validation results in requested format
def output_validation_results [
    summary: record
    validation_config: record
] {
    match $validation_config.output_format {
        "json" => {
            $summary | to json
        }
        "table" => {
            print $"Package Validation Results for ($validation_config.package_path)"
            print $"Overall Status: ($summary.overall_status)"
            print ""

            $summary.results | select category status issues warnings | table

            if ($summary.results | get issues | flatten | length) > 0 {
                print "\nIssues Found:"
                $summary.results | get issues | flatten | table
            }
        }
        _ => {
            # Default text format
            print $"Package Validation Results"
            print $"========================="
            print $"Package: ($validation_config.package_path)"
            print $"Validation Type: ($validation_config.validation_type)"
            print $"Overall Status: ($summary.overall_status)"
            print ""

            for result in $summary.results {
                print $"($result.category | str title): ($result.status | str upcase)"
                if ($result.issues | length) > 0 {
                    print $"  Issues: ($result.issues | length)"
                }
                if ($result.warnings | length) > 0 {
                    print $"  Warnings: ($result.warnings | length)"
                }
            }
        }
    }
}

# Quick validation command
def "main quick" [package_path: string] {
    main $package_path --validation-type quick
}

# Security validation command
def "main security" [package_path: string] {
    main $package_path --validation-type security
}
