# Platform Bootstrap System
# Ensures critical platform services are running before executing provisioning tasks
# Infrastructure-agnostic: supports Docker, Kubernetes, remote servers, etc.

use ../config/accessor.nu *
use ../config/context_manager.nu [get-active-workspace]
use ../setup/mod.nu [get-config-base-path]
use ../utils/logging.nu *
use ../utils/nickel_processor.nu [ncl-eval-soft]
use ../services/health.nu *
use ../services/lifecycle.nu *
use ../services/dependencies.nu *

# Load service deployment configuration
def get-service-config [service_name: string] {
    config-get $"platform.services.($service_name)" {
        name: $service_name
        health_check: "http"
        timeout: 30
        description: $"Platform service: ($service_name)"
    }
}

# Get deployment configuration from workspace
def get-deployment-config [] {
    # Try to load workspace-specific deployment config
    let workspace = (get-active-workspace)

    if ($workspace != null) {
        let workspace_config_path = ($workspace.path | path join "config" "platform" "deployment.toml")

        if ($workspace_config_path | path exists) {
            return (open $workspace_config_path)
        }
    }

    # Try to load platform deployment mode configuration (Nickel)
    let config_base = (get-config-base-path)
    let deployment_ncl = ($config_base | path join "platform" "deployment-mode.ncl")

    if ($deployment_ncl | path exists) {
        let content = (ncl-eval-soft $deployment_ncl [($env.PROVISIONING? | default "/usr/local/provisioning")] null)
        if $content != null {
            let deployment_mode = ($content.mode? | default "local")
            return {
                deployment: {
                    mode: $deployment_mode
                    location_type: (if $deployment_mode == "local" { "local" } else { "remote" })
                }
            }
        }
    }

    # Final fallback to defaults
    {
        deployment: {
            mode: "local"
            location_type: "local"
        }
    }
}

# Get deployment mode from configuration
def get-deployment-mode [] {
    let config = (get-deployment-config)
    $config.deployment.mode? | default "local"
}

# Get platform services deployment location
def get-deployment-location [] {
    let config = (get-deployment-config)
    $config.deployment? | default {
        mode: "local"
        location_type: "local"
    }
}

# Critical services that must be running for provisioning to work.
# Only the orchestrator is required for L2+ deployments; control-center
# and kms-service are optional platform features.
def get-critical-services []: nothing -> list<record> {
    let orchestrator_endpoint = (
        config-get "platform.orchestrator.endpoint" "http://localhost:9011/health"
    )

    [
        {
            name: "orchestrator"
            health_check: "http"
            endpoint: $orchestrator_endpoint
            timeout: 30
            description: "Workflow orchestrator"
        }
    ]
}

# Check if a service is healthy
def check-service-health [service: record] {
    match $service.health_check {
        "http" => {
            let result = (do {
                curl -s -f -m 5 $service.endpoint
            } | complete)
            $result.exit_code == 0
        }
        "tcp" => {
            let result = (do {
                ^nc -zv -w 2 ($service.endpoint | split row ":" | get 0) ($service.endpoint | split row ":" | get 1)
            } | complete)
            $result.exit_code == 0
        }
        _ => false
    }
}

# Helper to process a single service for bootstrap
def process-service-bootstrap [
    service: record
    auto_start: bool
    verbose: bool
    timeout: int
] {
    if $verbose {
        print $"📋 Checking ($service.name)..."
    }

    let is_healthy = (check-service-health $service)

    if $is_healthy {
        if $verbose {
            print $"   ✅ ($service.name) is healthy"
        }
        {
            name: $service.name
            status: "healthy"
            action: "none"
        }
    } else {
        if $verbose {
            print $"   ⚠️  ($service.name) is not responding"
        }

        if $auto_start {
            if $verbose {
                print $"   🚀 Starting ($service.name)..."
            }

            # Try to start the service
            let start_result = (
                not ((start-platform-service $service.name --verbose=$verbose) == null)
            )

            if $start_result {
                # Wait for service to be healthy
                let wait_result = (wait-for-service-health $service --timeout=$timeout --verbose=$verbose)

                if $wait_result {
                    if $verbose {
                        print $"   ✅ ($service.name) started successfully"
                    }
                    {
                        name: $service.name
                        status: "healthy"
                        action: "started"
                    }
                } else {
                    if $verbose {
                        print $"   ❌ ($service.name) failed to become healthy"
                    }
                    {
                        name: $service.name
                        status: "unhealthy"
                        action: "failed_to_start"
                    }
                }
            } else {
                if $verbose {
                    print $"   ❌ Failed to start ($service.name)"
                }
                {
                    name: $service.name
                    status: "unhealthy"
                    action: "start_failed"
                }
            }
        } else {
            {
                name: $service.name
                status: "unhealthy"
                action: "not_running"
            }
        }
    }
}

# Bootstrap platform services
export def bootstrap-platform [
    --auto-start (-a)           # Automatically start services if not running
    --force (-f)                # Force restart services
    --verbose (-v)              # Verbose output
    --timeout: int = 60         # Timeout in seconds
] {

    let critical_services = (get-critical-services)

    if $verbose {
        print $"🔧 Bootstrapping platform services..."
        print $"   Critical services: ($critical_services | length)"
        print ""
    }

    # Process each service using helper function to avoid closure variable capture
    let services_status = ($critical_services | each { |service|
        process-service-bootstrap $service $auto_start $verbose $timeout
    })

    # Check if all services are healthy
    let all_healthy = ($services_status | all { |s| $s.status == "healthy" })

    if $verbose {
        print ""
        print "🔍 Bootstrap Summary:"
        for status in $services_status {
            let status_icon = if $status.status == "healthy" { "✅" } else { "❌" }
            print $"   ($status_icon) ($status.name): ($status.status) (action: ($status.action))"
        }
        print ""
    }

    {
        all_healthy: $all_healthy
        services: $services_status
        timestamp: (date now)
    }
}

# Start a platform service based on deployment mode
def start-platform-service [
    service_name: string
    --verbose (-v)
] {
    let deployment_location = (get-deployment-location)
    let deployment_mode = (get-deployment-mode)

    if $verbose {
        print $"   Deployment mode: ($deployment_mode)"
        print $"   Deployment location: ($deployment_location.location_type)"
    }

    # Route to appropriate startup method based on deployment mode
    match $deployment_mode {
        "local" => { start-service-local $service_name --verbose=$verbose }
        "docker-compose" => { start-service-docker-compose $service_name --verbose=$verbose }
        "kubernetes" => { start-service-kubernetes $service_name --verbose=$verbose }
        "remote-ssh" => { start-service-remote-ssh $service_name --verbose=$verbose }
        "systemd" => { start-service-systemd $service_name --verbose=$verbose }
        _ => {
            if $verbose {
                print $"   ❌ Unknown deployment mode: ($deployment_mode)"
            }
            false
        }
    }
}

# Start service via Docker Compose
def start-service-docker-compose [
    service_name: string
    --verbose (-v)
] {
    let platform_path = (config-get "platform.docker_compose.path" (get-config-base-path | path join "platform"))
    let compose_file = ($platform_path | path join "docker-compose.yaml")

    if not ($compose_file | path exists) {
        if $verbose {
            print $"   ❌ Docker compose file not found: ($compose_file)"
        }
        return false
    }

    let result = (do {
        if $verbose {
            print $"   Running: docker-compose -f ($compose_file) up -d ($service_name)"
        }

        docker-compose -f $compose_file up -d $service_name
    } | complete)

    if $result.exit_code == 0 {
        true
    } else {
        if $verbose {
            print $"   Docker compose error: ($result.stderr)"
        }
        false
    }
}

# Start service locally via native binary or systemd
def start-service-local [
    service_name: string
    --verbose (-v)
] {
    let os_type = $nu.os-info.name

    # On Linux, try systemd first
    if $os_type == "linux" {
        let systemd_result = (do {
            if $verbose {
                print $"   Trying systemd: systemctl start ($service_name)"
            }
            systemctl start $service_name
        } | complete)

        if $systemd_result.exit_code == 0 {
            return true
        }
    }

    # Fallback (all OS): try binary in ~/.local/bin/ with provisioning- prefix
    let bin_dir = ($env.HOME | path join ".local" "bin")
    let local_bin = ($bin_dir | path join $"provisioning-($service_name)")
    let config_base = (get-config-base-path)
    let config_dir = ($config_base | path join "platform" "config")

    if ($local_bin | path exists) {
        if $verbose {
            print $"   Running binary: ($local_bin)"
            print $"   Config dir: ($config_dir)"
        }

        # Derive NICKEL_IMPORT_PATH from config base path automatically
        # Two cases:
        # 1. Development: /path/to/project/provisioning/../platform/config/
        #    → Look for provisioning/ at project root level
        # 2. User install: ~/.config/provisioning/platform/config/ (Linux) or
        #                  ~/Library/Application Support/provisioning/platform/config/ (macOS)
        #    → Use PROVISIONING env var pointing to the development project
        let nickel_import_path = (do {
            let normalized_config = ($config_base | path expand)
            # Go up 2 directories: config -> platform -> project_root
            let project_root = ($normalized_config | path dirname | path dirname)
            let provisioning_dir = ($project_root | path join "provisioning")

            # Case 1: Check if provisioning/ exists at project root level (local development)
            if ($provisioning_dir | path exists) {
                if $verbose {
                    print $"   NICKEL_IMPORT_PATH (local): ($provisioning_dir)"
                }
                $provisioning_dir
            } else {
                # Case 2: User install - check if in standard user config location by OS
                let config_str = ($normalized_config | into string)
                let os_type = $nu.os-info.name

                # Determine standard user config path for this OS
                let user_config_path = if $os_type == "linux" {
                    ($env.HOME | path join ".config" "provisioning")
                } else if $os_type == "macos" {
                    ($env.HOME | path join "Library" "Application Support" "provisioning")
                } else {
                    # Windows or other: try both paths
                    ($env.HOME | path join ".config" "provisioning")
                }

                let is_user_config = ($config_str | str starts-with ($user_config_path | path expand))

                if $is_user_config {
                    # For user installs, rely on PROVISIONING env var pointing to the development project
                    if $verbose {
                        print $"   User config location detected ($os_type): ($config_str)"
                        print $"   Using PROVISIONING env var for schemas"
                    }
                    $env.PROVISIONING? | default "/provisioning"
                } else {
                    # Fallback for other cases
                    if $verbose {
                        print $"   ⚠️  Could not determine provisioning location"
                    }
                    $env.PROVISIONING? | default "/provisioning"
                }
            }
        })

        let result = (do {
            if $verbose {
                # Show output during verbose mode for debugging
                with-env { NICKEL_IMPORT_PATH: $nickel_import_path } {
                    ^sh -c $"'($local_bin)' --config-dir '($config_dir)' &"
                }
            } else {
                with-env { NICKEL_IMPORT_PATH: $nickel_import_path } {
                    ^sh -c $"'($local_bin)' --config-dir '($config_dir)' > /dev/null 2>&1 &"
                }
            }
        } | complete)

        if $result.exit_code == 0 {
            return true
        } else {
            if $verbose {
                print $"   Error starting binary: ($result.stderr)"
            }
            return false
        }
    }

    if $verbose {
        print $"   ❌ Could not start ($service_name)"
        print $"      - systemd not available on ($os_type)"
        print $"      - binary not found: ($local_bin)"
    }
    false
}

# Start service via Kubernetes
def start-service-kubernetes [
    service_name: string
    --verbose (-v)
] {
    let kubeconfig = (config-get "platform.kubernetes.kubeconfig" "")
    let namespace = (config-get "platform.kubernetes.namespace" "default")
    let manifests_path = (config-get "platform.kubernetes.manifests_path" (get-config-base-path | path join "platform" "k8s"))

    if $verbose {
        print $"   Kubernetes namespace: ($namespace)"
        if ($kubeconfig | is-not-empty) {
            print $"   Using kubeconfig: ($kubeconfig)"
        }
    }

    # Try to find manifest for service
    let manifest_file = ($manifests_path | path join $"($service_name).yaml")

    if not ($manifest_file | path exists) {
        if $verbose {
            print $"   ⚠️  Manifest not found: ($manifest_file)"
            print $"   Trying: kubectl rollout restart deployment/($service_name)"
        }

        # Try to restart existing deployment
        let cmd = if ($kubeconfig | is-not-empty) {
            $"kubectl --kubeconfig=($kubeconfig) -n ($namespace) rollout restart deployment/($service_name)"
        } else {
            $"kubectl -n ($namespace) rollout restart deployment/($service_name)"
        }

        let result = (do {
            ^sh -c $cmd
        } | complete)

        if $result.exit_code == 0 {
            return true
        } else {
            if $verbose {
                print $"   ❌ Could not restart deployment"
            }
            return false
        }
    }

    # Apply manifest
    if $verbose {
        print $"   Applying manifest: ($manifest_file)"
    }

    let cmd = if ($kubeconfig | is-not-empty) {
        $"kubectl --kubeconfig=($kubeconfig) -n ($namespace) apply -f ($manifest_file)"
    } else {
        $"kubectl -n ($namespace) apply -f ($manifest_file)"
    }

    let result = (do {
        ^sh -c $cmd
    } | complete)

    if $result.exit_code == 0 {
        true
    } else {
        if $verbose {
            print $"   ❌ Error: could not apply Kubernetes manifest"
        }
        false
    }
}

# Start service via remote SSH
def start-service-remote-ssh [
    service_name: string
    --verbose (-v)
] {
    let remote_host = (config-get "platform.remote.host" "")
    let remote_user = (config-get "platform.remote.user" "root")
    let ssh_key = (config-get "platform.remote.ssh_key" "~/.ssh/id_rsa")
    let start_command = (config-get $"platform.remote.services.($service_name).start_command" "")

    if ($remote_host | is-empty) {
        if $verbose {
            print $"   ❌ Remote host not configured"
        }
        return false
    }

    if ($start_command | is-empty) {
        if $verbose {
            print $"   ❌ No start command configured for ($service_name)"
        }
        return false
    }

    if $verbose {
        print $"   SSH to ($remote_user)@($remote_host): ($start_command)"
    }

    let result = (do {
        ssh -i $ssh_key $"($remote_user)@($remote_host)" $start_command
    } | complete)

    if $result.exit_code == 0 {
        true
    } else {
        if $verbose {
            print $"   SSH error: ($result.stderr)"
        }
        false
    }
}

# Start service via systemd
def start-service-systemd [
    service_name: string
    --verbose (-v)
] {
    if $verbose {
        print $"   Running: systemctl start ($service_name)"
    }

    let result = (do {
        systemctl start $service_name
    } | complete)

    if $result.exit_code == 0 {
        true
    } else {
        if $verbose {
            print $"   ❌ Error: could not start service via systemctl"
        }
        false
    }
}

# Wait for a service to become healthy
def wait-for-service-health [
    service: record
    --timeout: int = 60
    --verbose (-v)
] {

    let start_time = (date now)
    let timeout_duration = ($timeout * 1_000_000_000)  # Convert to nanoseconds

    mut attempts = 0

    loop {
        let is_healthy = (check-service-health $service)
        $attempts = ($attempts + 1)

        if $is_healthy {
            if $verbose {
                print $"   ✅ Service became healthy after ($attempts) attempts"
            }
            return true
        }

        let elapsed = ((date now) - $start_time)
        let elapsed_ns = ($elapsed | into int)  # Convert duration to nanoseconds
        if $elapsed_ns > $timeout_duration {
            if $verbose {
                let timeout_str = ($timeout | into string)
                let msg = $"Timeout after ($attempts) attempts ($timeout_str) seconds"
                print $"   ⏱️  ($msg)"
            }
            return false
        }

        if $verbose and (($attempts mod 3) == 0) {
            print $"   ⏳ Waiting... attempt ($attempts)"
        }

        sleep 2sec
    }

    false  # Fallback - should never reach here due to loop returns
}

# Get platform service status summary
export def platform-status [
    --verbose (-v)
] {

    let critical_services = (get-critical-services)
    mut status_details = []

    for service in $critical_services {
        let is_healthy = (check-service-health $service)
        $status_details = ($status_details | append {
            name: $service.name
            description: $service.description
            healthy: $is_healthy
            endpoint: $service.endpoint
        })
    }

    let all_healthy = ($status_details | all {|s| $s.healthy})

    if $verbose {
        print "Platform Services Status:"
        print "=========================="
        for service in $status_details {
            let icon = if $service.healthy { "✅" } else { "❌" }
            print $"($icon) ($service.name)"
            print $"   Description: ($service.description)"
            print $"   Endpoint: ($service.endpoint)"
            print $"   Status: (if $service.healthy { "healthy" } else { "unhealthy" })"
            print ""
        }
    }

    {
        all_healthy: $all_healthy
        services: $status_details
        timestamp: (date now)
    }
}
