# Taskserv Validation Framework
# Multi-level validation for taskservs before deployment

# REMOVED: use lib_provisioning * - causes circular import
use utils.nu *
use deps_validator.nu *
use ../lib_provisioning/config/accessor.nu *

# Validation levels
const VALIDATION_LEVELS = {
    static: "Static validation (Nickel, templates, scripts)"
    dependencies: "Dependency validation"
    prerequisites: "Server prerequisites validation"
    health: "Health check validation"
    all: "Complete validation (all levels)"
}

# Validate Nickel schemas for taskserv
def validate-nickel-schemas [
    taskserv_name: string
    --verbose (-v)
] {
    let taskservs_path = (get-taskservs-path)
    let taskserv_dir = (find-taskserv-dir $taskservs_path $taskserv_name)
    let schema_path = if ($taskserv_dir | is-not-empty) { $taskserv_dir | path join "nickel" } else { "" }

    if not ($schema_path | path exists) {
        return {
            valid: false
            level: "nickel"
            errors: [$"Nickel directory not found: ($schema_path)"]
            warnings: []
        }
    }

    # Find all .ncl files
    let nickel_files = (glob ($schema_path | path join "*.ncl"))

    if ($nickel_files | is-empty) {
        return {
            valid: false
            level: "nickel"
            errors: [$"No Nickel files found in: ($schema_path)"]
            warnings: []
        }
    }

    if $verbose {
        _print $"Validating Nickel schemas for (_ansi yellow_bold)($taskserv_name)(_ansi reset)..."
    }

    mut errors = []
    mut warnings = []

    for file in $nickel_files {
        if $verbose {
            _print $"  Checking ($file | path basename)..."
        }

        let decl_check = (do { ^nickel typecheck $file } | complete)

        if $decl_check.exit_code == 0 {
            if $verbose {
                _print $"    ✓ Valid"
            }
        } else {
            let error_msg = $decl_check.stderr
            $errors = ($errors | append $"Nickel error in ($file | path basename): ($error_msg)")
            if $verbose {
                _print $"    ✗ Error: ($error_msg)"
            }
        }
    }

    return {
        valid: (($errors | length) == 0)
        level: "nickel"
        files_checked: ($nickel_files | length)
        errors: $errors
        warnings: $warnings
    }
}

# Validate Jinja2 templates
def validate-templates [
    taskserv_name: string
    --verbose (-v)
] {
    let taskservs_path = (get-taskservs-path)
    let taskserv_dir = (find-taskserv-dir $taskservs_path $taskserv_name)
    let default_path = if ($taskserv_dir | is-not-empty) { $taskserv_dir | path join "default" } else { "" }

    if not ($default_path | path exists) {
        return {
            valid: true
            level: "templates"
            files_checked: 0
            errors: []
            warnings: ["No default directory found, skipping template validation"]
        }
    }

    # Find all .j2 files
    let template_files = (glob ($default_path | path join "**/*.j2"))

    if ($template_files | is-empty) {
        return {
            valid: true
            level: "templates"
            files_checked: 0
            errors: []
            warnings: ["No templates found"]
        }
    }

    if $verbose {
        _print $"Validating templates for (_ansi yellow_bold)($taskserv_name)(_ansi reset)..."
    }

    mut errors = []
    mut warnings = []

    for file in $template_files {
        if $verbose {
            _print $"  Checking ($file | path basename)..."
        }

        # Basic syntax check - read and check for common issues
        let content = (do -i { open -r $file } | default "")
        if ($content | is-empty) {
            $errors = ($errors | append $"Cannot read template: ($file | path basename)")
            continue
        }

        # Check for unclosed Jinja2 tags
        let open_blocks = ($content | str replace --all '\{\%.*?\%\}' '' | str replace --all '\{\{.*?\}\}' '')
        if ($open_blocks | str contains '{{') or ($open_blocks | str contains '{%') {
            $warnings = ($warnings | append $"Potential unclosed Jinja2 tags in: ($file | path basename)")
        }

        if $verbose {
            _print $"    ✓ Basic syntax OK"
        }
    }

    return {
        valid: (($errors | length) == 0)
        level: "templates"
        files_checked: ($template_files | length)
        errors: $errors
        warnings: $warnings
    }
}

# Validate shell scripts
def validate-scripts [
    taskserv_name: string
    --verbose (-v)
] {
    let taskservs_path = (get-taskservs-path)
    let taskserv_dir = (find-taskserv-dir $taskservs_path $taskserv_name)
    let default_path = if ($taskserv_dir | is-not-empty) { $taskserv_dir | path join "default" } else { "" }

    if not ($default_path | path exists) {
        return {
            valid: true
            level: "scripts"
            files_checked: 0
            errors: []
            warnings: ["No default directory found, skipping script validation"]
        }
    }

    # Find all .sh files
    let script_files = (glob ($default_path | path join "**/*.sh"))

    if ($script_files | is-empty) {
        return {
            valid: true
            level: "scripts"
            files_checked: 0
            errors: []
            warnings: ["No shell scripts found"]
        }
    }

    if $verbose {
        _print $"Validating scripts for (_ansi yellow_bold)($taskserv_name)(_ansi reset)..."
    }

    mut errors = []
    mut warnings = []

    # Check if shellcheck is available
    let has_shellcheck = (which shellcheck | length) > 0

    if not $has_shellcheck {
        $warnings = ($warnings | append "shellcheck not available, skipping detailed script validation")
    }

    for file in $script_files {
        if $verbose {
            _print $"  Checking ($file | path basename)..."
        }

        # Check if file is executable
        let is_executable = (do -i { ls -l $file | get mode | first | str contains "x" } | default false)

        if not $is_executable {
            $warnings = ($warnings | append $"Script not executable: ($file | path basename)")
        }

        # Run shellcheck if available
        if $has_shellcheck {
            let shellcheck_result = (do {
                ^shellcheck --severity=error $file
            } | complete)

            if $shellcheck_result.exit_code == 0 {
                if $verbose {
                    _print $"    ✓ shellcheck passed"
                }
            } else {
                $errors = ($errors | append $"shellcheck error in ($file | path basename): ($shellcheck_result.stderr)")
                if $verbose {
                    _print $"    ✗ shellcheck failed"
                }
            }
        } else if $verbose {
            _print $"    ⊘ shellcheck skipped"
        }
    }

    return {
        valid: (($errors | length) == 0)
        level: "scripts"
        files_checked: ($script_files | length)
        has_shellcheck: $has_shellcheck
        errors: $errors
        warnings: $warnings
    }
}

# Validate health check configuration
def validate-health-check [
    taskserv_name: string
    settings: record
    --verbose (-v)
] {
    if $verbose {
        _print $"Validating health check for (_ansi yellow_bold)($taskserv_name)(_ansi reset)..."
    }

    let deps_validation = (validate-dependencies $taskserv_name $settings --verbose=false)

    if not $deps_validation.has_dependencies {
        return {
            valid: true
            level: "health"
            has_health_check: false
            errors: []
            warnings: ["No health check configuration found"]
        }
    }

    let health_check = ($deps_validation.health_check | default null)

    if $health_check == null {
        return {
            valid: true
            level: "health"
            has_health_check: false
            errors: []
            warnings: ["No health check configuration in dependencies"]
        }
    }

    mut errors = []
    mut warnings = []

    let endpoint = ($health_check | get -o endpoint | default "")
    let timeout = ($health_check | get -o timeout | default 30)
    let interval = ($health_check | get -o interval | default 10)

    if $endpoint == "" {
        $errors = ($errors | append "Health check endpoint is empty")
    } else {
        if not ($endpoint | str starts-with "http://") and not ($endpoint | str starts-with "https://") {
            $warnings = ($warnings | append "Health check endpoint should use http:// or https://")
        }

        if $verbose {
            _print $"  Endpoint: ($endpoint)"
            _print $"  Timeout: ($timeout)s"
            _print $"  Interval: ($interval)s"
        }
    }

    if $timeout <= 0 {
        $errors = ($errors | append "Health check timeout must be positive")
    }

    if $interval <= 0 {
        $errors = ($errors | append "Health check interval must be positive")
    }

    return {
        valid: (($errors | length) == 0)
        level: "health"
        has_health_check: true
        endpoint: $endpoint
        timeout: $timeout
        interval: $interval
        errors: $errors
        warnings: $warnings
    }
}

# Public entry point for check_mode.nu — aggregates the three internal validators
export def run-static-validation [
    taskserv_name: string
    --verbose (-v)
]: nothing -> record {
    {
        nickel: (validate-nickel-schemas $taskserv_name --verbose=$verbose)
        templates: (validate-templates $taskserv_name --verbose=$verbose)
        scripts: (validate-scripts $taskserv_name --verbose=$verbose)
    }
}

# Main validation command
export def "main validate" [
    taskserv_name: string
    --infra (-i): string
    --settings (-s): string
    --level (-l): string = "all"
    --verbose (-v)
    --out: string
] {
    if ($out | is-not-empty) {
        set-provisioning-out $out
        set-provisioning-no-terminal true
    }

    # Load settings
    let settings_result = (do {
        find_get_settings --infra $infra --settings $settings
    } | complete)

    if $settings_result.exit_code != 0 {
        _print $"🛑 Failed to load settings"
        return
    }

    let curr_settings = $settings_result.stdout

    _print $"\n(_ansi cyan_bold)Taskserv Validation(_ansi reset)"
    _print $"Taskserv: (_ansi yellow_bold)($taskserv_name)(_ansi reset)"
    _print $"Level: ($level)\n"

    # Validate level parameter
    if $level not-in ["static", "dependencies", "prerequisites", "health", "all"] {
        _print $"🛑 Invalid level: ($level)"
        _print $"Valid levels: (($VALIDATION_LEVELS | columns | str join ', '))"
        return
    }

    mut all_results = []

    # Static validation (Nickel, templates, scripts)
    if $level in ["static", "all"] {
        let decl_result = (validate-nickel-schemas $taskserv_name --verbose=$verbose)
        $all_results = ($all_results | append $decl_result)

        let template_result = (validate-templates $taskserv_name --verbose=$verbose)
        $all_results = ($all_results | append $template_result)

        let script_result = (validate-scripts $taskserv_name --verbose=$verbose)
        $all_results = ($all_results | append $script_result)
    }

    # Dependencies validation
    if $level in ["dependencies", "all"] {
        let deps_result = (validate-dependencies $taskserv_name $curr_settings --verbose=$verbose)
        $all_results = ($all_results | append ($deps_result | insert level "dependencies"))

        if $verbose or not $deps_result.valid {
            print-validation-report $deps_result
        }
    }

    # Health check validation
    if $level in ["health", "all"] {
        let health_result = (validate-health-check $taskserv_name $curr_settings --verbose=$verbose)
        $all_results = ($all_results | append $health_result)
    }

    # Print summary
    _print $"\n(_ansi cyan_bold)Validation Summary(_ansi reset)"

    let total_errors = ($all_results | get errors | flatten | length)
    let total_warnings = ($all_results | get warnings | flatten | length)

    for result in $all_results {
        let level_name = $result.level
        let status = if $result.valid {
            $"(_ansi green_bold)✓(_ansi reset)"
        } else {
            $"(_ansi red_bold)✗(_ansi reset)"
        }

        let err_count = ($result.errors | length)
        let warn_count = ($result.warnings | length)

        _print $"($status) ($level_name): ($err_count) errors, ($warn_count) warnings"

        if $err_count > 0 {
            for err in $result.errors {
                _print $"    (_ansi red)✗(_ansi reset) ($err)"
            }
        }

        if $warn_count > 0 and $verbose {
            for warn in $result.warnings {
                _print $"    (_ansi yellow)⚠(_ansi reset) ($warn)"
            }
        }
    }

    _print $"\n(_ansi cyan_bold)Overall Status(_ansi reset)"
    if $total_errors == 0 {
        _print $"(_ansi green_bold)✓ VALID(_ansi reset) - ($total_warnings) warnings"
    } else {
        _print $"(_ansi red_bold)✗ INVALID(_ansi reset) - ($total_errors) errors, ($total_warnings) warnings"
    }
}

# Check dependencies command
export def "main check-deps" [
    taskserv_name: string
    --infra (-i): string
    --settings (-s): string
    --verbose (-v)
] {
    let settings_result = (do {
        find_get_settings --infra $infra --settings $settings
    } | complete)

    if $settings_result.exit_code != 0 {
        _print $"🛑 Failed to load settings"
        return
    }

    let curr_settings = $settings_result.stdout

    let validation = (validate-infra-dependencies $taskserv_name $curr_settings --verbose=$verbose)
    print-validation-report $validation
}

# List validation levels
export def "main levels" [] {
    _print $"\n(_ansi cyan_bold)Available Validation Levels(_ansi reset)\n"

    for level in ($VALIDATION_LEVELS | transpose name description) {
        _print $"(_ansi yellow_bold)($level.name)(_ansi reset)"
        _print $"  ($level.description)\n"
    }
}
