# Configuration validation - Checks config structure, types, paths, and semantic rules
# NUSHELL 0.109 COMPLIANT - Using reduce --fold (Rule 3), do-complete (Rule 5), each (Rule 8)

# Validate configuration structure - checks required sections exist
export def validate-config-structure [config: record]: nothing -> record {
    let required_sections = ["core", "paths", "debug", "sops"]

    # Use reduce --fold to collect errors (Rule 3: no mutable variables)
    let validation_result = ($required_sections | reduce --fold {errors: [], warnings: []} {|section, result|
        let section_result = (do { $config | get $section } | complete)
        let section_value = if $section_result.exit_code == 0 { $section_result.stdout } else { null }

        if ($section_value | is-empty) {
            $result | upsert errors ($result.errors | append {
                type: "missing_section",
                severity: "error",
                section: $section,
                message: $"Missing required configuration section: ($section)"
            })
        } else {
            $result
        }
    })

    {
        valid: (($validation_result.errors | length) == 0),
        errors: $validation_result.errors,
        warnings: $validation_result.warnings
    }
}

# Validate path values - checks paths exist and are absolute
export def validate-path-values [config: record]: nothing -> record {
    let required_paths = ["base", "providers", "taskservs", "clusters"]

    let paths_result = (do { $config | get paths } | complete)
    let paths = if $paths_result.exit_code == 0 { $paths_result.stdout } else { {} }

    # Collect validation errors and warnings (Rule 3: using reduce --fold)
    let validation_result = ($required_paths | reduce --fold {errors: [], warnings: []} {|path_name, result|
        let path_result = (do { $paths | get $path_name } | complete)
        let path_value = if $path_result.exit_code == 0 { $path_result.stdout } else { null }

        if ($path_value | is-empty) {
            $result | upsert errors ($result.errors | append {
                type: "missing_path",
                severity: "error",
                path: $path_name,
                message: $"Missing required path: paths.($path_name)"
            })
        } else {
            # Check if path is absolute
            let abs_result = if not ($path_value | str starts-with "/") {
                $result | upsert warnings ($result.warnings | append {
                    type: "relative_path",
                    severity: "warning",
                    path: $path_name,
                    value: $path_value,
                    message: $"Path paths.($path_name) should be absolute, got: ($path_value)"
                })
            } else {
                $result
            }

            # Check if base path exists (critical for system operation)
            if $path_name == "base" and not ($path_value | path exists) {
                $abs_result | upsert errors ($abs_result.errors | append {
                    type: "path_not_exists",
                    severity: "error",
                    path: $path_name,
                    value: $path_value,
                    message: $"Base path does not exist: ($path_value)"
                })
            } else {
                $abs_result
            }
        }
    })

    {
        valid: (($validation_result.errors | length) == 0),
        errors: $validation_result.errors,
        warnings: $validation_result.warnings
    }
}

# Validate data types - checks configuration values have correct types
export def validate-data-types [config: record]: nothing -> record {
    let type_checks = [
        { field: "core.version", expected: "string", validator: {|v|
            let parts = ($v | split row ".")
            ($parts | length) >= 3
        }},
        { field: "debug.enabled", expected: "bool" },
        { field: "debug.metadata", expected: "bool" },
        { field: "sops.use_sops", expected: "bool" }
    ]

    # Validate each type check (Rule 3: using reduce --fold, Rule 8: using each)
    let validation_result = ($type_checks | reduce --fold {errors: [], warnings: []} {|check, result|
        let field_result = (do {
            let parts = ($check.field | split row ".")
            if ($parts | length) == 2 {
                $config | get ($parts | first) | get ($parts | last)
            } else {
                $config | get $check.field
            }
        } | complete)

        let value = if $field_result.exit_code == 0 { $field_result.stdout } else { null }

        if ($value | is-empty) {
            $result
        } else {
            let actual_type = ($value | describe)
            let type_matches = if ($check.expected == "bool") {
                $actual_type == "bool"
            } else if ($check.expected == "string") {
                $actual_type == "string"
            } else {
                $actual_type == $check.expected
            }

            if not $type_matches {
                $result | upsert errors ($result.errors | append {
                    type: "invalid_type",
                    severity: "error",
                    field: $check.field,
                    value: $value,
                    expected: $check.expected,
                    actual: $actual_type,
                    message: $"($check.field) must be ($check.expected), got: ($actual_type)"
                })
            } else if ($check.validator? != null) {
                # Additional validation via closure (if provided)
                if (($check.validator | call $value)) {
                    $result
                } else {
                    $result | upsert errors ($result.errors | append {
                        type: "invalid_value",
                        severity: "error",
                        field: $check.field,
                        value: $value,
                        message: $"($check.field) has invalid value: ($value)"
                    })
                }
            } else {
                $result
            }
        }
    })

    {
        valid: (($validation_result.errors | length) == 0),
        errors: $validation_result.errors,
        warnings: $validation_result.warnings
    }
}

# Validate semantic rules - business logic validation
export def validate-semantic-rules [config: record]: nothing -> record {
    let providers_result = (do { $config | get providers } | complete)
    let providers = if $providers_result.exit_code == 0 { $providers_result.stdout } else { {} }
    let default_result = (do { $providers | get default } | complete)
    let default_provider = if $default_result.exit_code == 0 { $default_result.stdout } else { null }

    # Validate provider
    let provider_check = if ($default_provider | is-not-empty) {
        let valid_providers = ["aws", "upcloud", "local"]
        if ($default_provider in $valid_providers) {
            {errors: [], warnings: []}
        } else {
            {
                errors: [{
                    type: "invalid_provider",
                    severity: "error",
                    field: "providers.default",
                    value: $default_provider,
                    valid_options: $valid_providers,
                    message: $"Invalid default provider: ($default_provider)"
                }],
                warnings: []
            }
        }
    } else {
        {errors: [], warnings: []}
    }

    # Validate log level
    let log_level_result = (do { $config | get debug.log_level } | complete)
    let log_level = if $log_level_result.exit_code == 0 { $log_level_result.stdout } else { null }

    let log_check = if ($log_level | is-not-empty) {
        let valid_levels = ["trace", "debug", "info", "warn", "error"]
        if ($log_level in $valid_levels) {
            {errors: [], warnings: []}
        } else {
            {
                errors: [],
                warnings: [{
                    type: "invalid_log_level",
                    severity: "warning",
                    field: "debug.log_level",
                    value: $log_level,
                    valid_options: $valid_levels,
                    message: $"Invalid log level: ($log_level)"
                }]
            }
        }
    } else {
        {errors: [], warnings: []}
    }

    # Validate output format
    let output_result = (do { $config | get output.format } | complete)
    let output_format = if $output_result.exit_code == 0 { $output_result.stdout } else { null }

    let format_check = if ($output_format | is-not-empty) {
        let valid_formats = ["json", "yaml", "toml", "text"]
        if ($output_format in $valid_formats) {
            {errors: [], warnings: []}
        } else {
            {
                errors: [],
                warnings: [{
                    type: "invalid_output_format",
                    severity: "warning",
                    field: "output.format",
                    value: $output_format,
                    valid_options: $valid_formats,
                    message: $"Invalid output format: ($output_format)"
                }]
            }
        }
    } else {
        {errors: [], warnings: []}
    }

    # Combine all semantic checks (Rule 3: immutable combination)
    let all_errors = (
        $provider_check.errors | append $log_check.errors | append $format_check.errors
    )
    let all_warnings = (
        $provider_check.warnings | append $log_check.warnings | append $format_check.warnings
    )

    {
        valid: (($all_errors | length) == 0),
        errors: $all_errors,
        warnings: $all_warnings
    }
}

# Validate file existence - checks referenced files exist
export def validate-file-existence [config: record]: nothing -> record {
    # Check SOPS configuration file
    let sops_cfg_result = (do { $config | get sops.config_path } | complete)
    let sops_config = if $sops_cfg_result.exit_code == 0 { $sops_cfg_result.stdout } else { null }

    let sops_config_check = if ($sops_config | is-not-empty) and not ($sops_config | path exists) {
        [{
            type: "missing_sops_config",
            severity: "warning",
            field: "sops.config_path",
            value: $sops_config,
            message: $"SOPS config file not found: ($sops_config)"
        }]
    } else {
        []
    }

    # Check SOPS key files
    let key_result = (do { $config | get sops.key_search_paths } | complete)
    let key_paths = if $key_result.exit_code == 0 { $key_result.stdout } else { [] }

    let key_found = ($key_paths
        | any {|key_path|
            let expanded_path = ($key_path | str replace "~" $env.HOME)
            ($expanded_path | path exists)
        }
    )

    let sops_key_check = if not $key_found and ($key_paths | length) > 0 {
        [{
            type: "missing_sops_keys",
            severity: "warning",
            field: "sops.key_search_paths",
            value: $key_paths,
            message: $"No SOPS key files found in search paths"
        }]
    } else {
        []
    }

    # Check critical configuration files
    let settings_result = (do { $config | get paths.files.settings } | complete)
    let settings_file = if $settings_result.exit_code == 0 { $settings_result.stdout } else { null }

    let settings_check = if ($settings_file | is-not-empty) and not ($settings_file | path exists) {
        [{
            type: "missing_settings_file",
            severity: "error",
            field: "paths.files.settings",
            value: $settings_file,
            message: $"Settings file not found: ($settings_file)"
        }]
    } else {
        []
    }

    # Combine all checks (Rule 3: immutable combination)
    let all_errors = $settings_check
    let all_warnings = ($sops_config_check | append $sops_key_check)

    {
        valid: (($all_errors | length) == 0),
        errors: $all_errors,
        warnings: $all_warnings
    }
}

# Main validation function - runs all validation checks
export def validate-config [
    config: record
    --detailed = false              # Show detailed validation results
    --strict = false                # Treat warnings as errors
]: nothing -> record {
    # Run all validation checks
    let structure_result = (validate-config-structure $config)
    let paths_result = (validate-path-values $config)
    let types_result = (validate-data-types $config)
    let semantic_result = (validate-semantic-rules $config)
    let files_result = (validate-file-existence $config)

    # Combine all results using immutable appending (Rule 3)
    let all_errors = (
        $structure_result.errors | append $paths_result.errors | append $types_result.errors |
        append $semantic_result.errors | append $files_result.errors
    )

    let all_warnings = (
        $structure_result.warnings | append $paths_result.warnings | append $types_result.warnings |
        append $semantic_result.warnings | append $files_result.warnings
    )

    let has_errors = ($all_errors | length) > 0
    let has_warnings = ($all_warnings | length) > 0

    # In strict mode, treat warnings as errors
    let final_valid = if $strict {
        (not $has_errors) and (not $has_warnings)
    } else {
        not $has_errors
    }

    # Throw error if validation fails and not in detailed mode
    if (not $detailed) and (not $final_valid) {
        let error_messages = ($all_errors | each { |err| $err.message })
        let warning_messages = if $strict { ($all_warnings | each { |warn| $warn.message }) } else { [] }
        let combined_messages = ($error_messages | append $warning_messages)

        error make {
            msg: ($combined_messages | str join "; ")
        }
    }

    # Return detailed results
    {
        valid: $final_valid,
        errors: $all_errors,
        warnings: $all_warnings,
        summary: {
            total_errors: ($all_errors | length),
            total_warnings: ($all_warnings | length),
            checks_run: 5,
            structure_valid: $structure_result.valid,
            paths_valid: $paths_result.valid,
            types_valid: $types_result.valid,
            semantic_valid: $semantic_result.valid,
            files_valid: $files_result.valid
        }
    }
}
