#!/usr/bin/env nu

# Service Lifecycle Management
# Handles starting and stopping services based on deployment mode

def get-service-pid-dir [] {
    $"($env.HOME)/.provisioning/services/pids"
}

def get-service-log-dir [] {
    $"($env.HOME)/.provisioning/services/logs"
}

# Start service based on deployment mode
export def start-service-by-mode [
    service_def: record
    service_name: string
] {
    match $service_def.deployment.mode {
        "binary" => {
            start-binary-service $service_def $service_name
        }
        "docker" => {
            start-docker-service $service_def $service_name
        }
        "docker-compose" => {
            start-docker-compose-service $service_def $service_name
        }
        "kubernetes" => {
            start-kubernetes-service $service_def $service_name
        }
        "remote" => {
            # Remote services are not started locally
            print $"Service '($service_name)' is remote - checking availability..."
            true
        }
        _ => {
            print $"Unknown deployment mode: ($service_def.deployment.mode)"
            false
        }
    }
}

# Start binary service
def start-binary-service [
    service_def: record
    service_name: string
] {
    let binary_config = $service_def.deployment.binary
    let binary_path = ($binary_config.binary_path | str replace -a '${HOME}' $env.HOME)

    # Expand binary path
    let binary_path = if ($binary_path | str starts-with '~') {
        $binary_path | str replace '~' $env.HOME
    } else {
        $binary_path
    }

    if not ($binary_path | path exists) {
        print $"Binary not found: ($binary_path)"
        return false
    }

    let working_dir = if "working_dir" in $binary_config {
        $binary_config.working_dir | str replace -a '${HOME}' $env.HOME
    } else {
        $env.PWD
    }

    let log_dir = (get-service-log-dir)
    let log_file = $"($log_dir)/($service_name).log"
    let pid_dir = (get-service-pid-dir)
    let pid_file = $"($pid_dir)/($service_name).pid"

    # Build command with args
    let args = $binary_config.args | str join ' '
    let env_vars = if "env" in $binary_config {
        $binary_config.env | transpose key value | each { |row|
            $"($row.key)=($row.value)"
        } | str join ' '
    } else {
        ""
    }

    # Start in background
    let cmd = if ($env_vars | is-empty) {
        $"($binary_path) ($args) >> ($log_file) 2>&1 & echo $!"
    } else {
        $"($env_vars) ($binary_path) ($args) >> ($log_file) 2>&1 & echo $!"
    }

    let result = (do {
        bash -c $cmd | str trim
    } | complete)

    if $result.exit_code == 0 {
        let pid = $result.stdout
        $pid | save -f $pid_file

        sleep 1sec

        # Verify process started
        let pid_int = ($pid | into int)
        if (ps | where pid == $pid_int | length) > 0 {
            print $"✅ Started [$service_name] with PID [$pid]"
            true
        } else {
            print $"❌ Failed to start [$service_name]"
            false
        }
    } else {
        print $"❌ Error starting [$service_name]: ($result.stderr)"
        false
    }
}

# Start Docker service
def start-docker-service [
    service_def: record
    service_name: string
] {
    let docker_config = $service_def.deployment.docker

    # Check if container already exists
    let existing = (docker ps -a --filter $"name=($docker_config.container_name)" --format "{{.Names}}" | lines)

    if ($docker_config.container_name in $existing) {
        # Container exists, try to start it
        let result = (do {
            docker start $docker_config.container_name
        } | complete)

        if $result.exit_code == 0 {
            print $"✅ Started existing container: ($docker_config.container_name)"
            return true
        } else {
            # Remove old container and create new one
            print $"Removing old container: ($docker_config.container_name)"
            docker rm -f $docker_config.container_name
        }
    }

    # Build docker run command by collecting all components
    let restart_policy = $docker_config.restart_policy? | default "unless-stopped"

    let port_args = (
        $docker_config.ports
        | flat-map {|port| ["-p", $port]}
    )

    let volume_args = (
        $docker_config.volumes
        | each {|volume|
            let expanded = ($volume | str replace -a '${HOME}' $env.HOME)
            ["-v", $expanded]
        }
        | flatten
    )

    let env_args = if "environment" in $docker_config {
        ($docker_config.environment | columns)
        | each {|key|
            let value = ($docker_config.environment | get $key)
            ["-e", $"($key)=($value)"]
        }
        | flatten
    } else {
        []
    }

    let network_args = if "networks" in $docker_config {
        $docker_config.networks
        | flat-map {|network| ["--network", $network]}
    } else {
        []
    }

    let command_args = if "command" in $docker_config {
        $docker_config.command
    } else {
        []
    }

    let cmd = [
        "docker", "run", "-d",
        "--name", $docker_config.container_name,
        "--restart", $restart_policy
    ]
    | append $port_args
    | append $volume_args
    | append $env_args
    | append $network_args
    | append $docker_config.image
    | if ($command_args | is-not-empty) {
        append $command_args
    } else {
        .
    }

    let result = (do {
        run-external ...$cmd
    } | complete)

    if $result.exit_code == 0 {
        print $"✅ Started Docker container: ($docker_config.container_name)"
        true
    } else {
        print $"❌ Failed to start Docker container: ($docker_config.container_name) - ($result.stderr)"
        false
    }
}

# Start Docker Compose service
def start-docker-compose-service [
    service_def: record
    service_name: string
] {
    let compose_config = $service_def.deployment.docker_compose
    let compose_file = ($compose_config.compose_file | str replace -a '${HOME}' $env.HOME)

    if not ($compose_file | path exists) {
        print $"Compose file not found: ($compose_file)"
        return false
    }

    let project_name = $compose_config.project_name? | default "provisioning"

    let result = if "env_file" in $compose_config {
        let env_file = ($compose_config.env_file | str replace -a '${HOME}' $env.HOME)
        (do {
            docker compose -f $compose_file -p $project_name --env-file $env_file up -d $compose_config.service_name
        } | complete)
    } else {
        (do {
            docker compose -f $compose_file -p $project_name up -d $compose_config.service_name
        } | complete)
    }

    if $result.exit_code == 0 {
        print $"✅ Started Docker Compose service: ($compose_config.service_name)"
        true
    } else {
        print $"❌ Failed to start Docker Compose service: ($compose_config.service_name) - ($result.stderr)"
        false
    }
}

# Start Kubernetes service
def start-kubernetes-service [
    service_def: record
    service_name: string
] {
    let k8s_config = $service_def.deployment.kubernetes

    let kubeconfig = if "kubeconfig" in $k8s_config {
        ["--kubeconfig", $k8s_config.kubeconfig]
    } else {
        []
    }

    # Check if namespace exists
    let ns_check = (do {
        kubectl ...$kubeconfig get namespace $k8s_config.namespace
    } | complete)

    if $ns_check.exit_code != 0 {
        print $"Creating namespace: ($k8s_config.namespace)"
        kubectl ...$kubeconfig create namespace $k8s_config.namespace
    }

    # Apply manifests if provided
    if "manifests_path" in $k8s_config {
        let manifests_path = ($k8s_config.manifests_path | str replace -a '${HOME}' $env.HOME)

        if ($manifests_path | path exists) {
            let result = (do {
                kubectl ...$kubeconfig apply -f $manifests_path -n $k8s_config.namespace
            } | complete)

            if $result.exit_code == 0 {
                print $"✅ Applied manifests for: [$service_name]"
            } else {
                print $"❌ Failed to apply manifests for: [$service_name] - ($result.stderr)"
                return false
            }
        }
    }

    # Install Helm chart if provided
    if "helm_chart" in $k8s_config {
        let helm_config = $k8s_config.helm_chart

        let base_helm_cmd = ["helm", "install", $helm_config.release_name, $helm_config.chart, "-n", $k8s_config.namespace]

        if "repo_url" in $helm_config {
            # Add repo first
            let repo_result = (do {
                helm repo add $service_name $helm_config.repo_url
                helm repo update
            } | complete)

            if $repo_result.exit_code != 0 {
                print $"Warning: Could not add Helm repo - ($repo_result.stderr)"
            }
        }

        let with_version = if "version" in $helm_config {
            $base_helm_cmd | append ["--version", $helm_config.version]
        } else {
            $base_helm_cmd
        }

        let helm_cmd = if "values_file" in $helm_config {
            let values_file = ($helm_config.values_file | str replace -a '${HOME}' $env.HOME)
            $with_version | append ["-f", $values_file]
        } else {
            $with_version
        }

        let result = (do {
            run-external ...$helm_cmd
        } | complete)

        if $result.exit_code == 0 {
            print $"✅ Installed Helm chart: ($helm_config.chart)"
            true
        } else {
            print $"❌ Failed to install Helm chart: ($helm_config.chart) - ($result.stderr)"
            false
        }
    } else {
        true
    }
}

# Stop service by mode
export def stop-service-by-mode [
    service_name: string
    service_def: record
    force: bool = false
] {
    match $service_def.deployment.mode {
        "binary" => {
            stop-binary-service $service_name $force
        }
        "docker" => {
            stop-docker-service $service_def $force
        }
        "docker-compose" => {
            stop-docker-compose-service $service_def
        }
        "kubernetes" => {
            stop-kubernetes-service $service_def $force
        }
        "remote" => {
            print $"Service '($service_name)' is remote - cannot stop"
            true
        }
        _ => {
            print $"Unknown deployment mode: ($service_def.deployment.mode)"
            false
        }
    }
}

# Stop binary service
def stop-binary-service [
    service_name: string
    force: bool
] {
    let pid_dir = (get-service-pid-dir)
    let pid_file = $"($pid_dir)/($service_name).pid"

    if not ($pid_file | path exists) {
        print $"No PID file found for ($service_name)"
        return true
    }

    let pid = (open $pid_file | str trim | into int)

    if (ps | where pid == $pid | length) == 0 {
        print $"Process [$pid] not found"
        rm $pid_file
        return true
    }

    let result = (do {
        if $force {
            kill -9 $pid
        } else {
            kill $pid
        }
    } | complete)

    if $result.exit_code == 0 {
        sleep 2sec

        # Check if still running
        if (ps | where pid == $pid | length) > 0 {
            print $"Process still running, force killing..."
            kill -9 $pid
            sleep 1sec
        }

        rm $pid_file
        print $"✅ Stopped [$service_name]"
        true
    } else {
        print $"❌ Failed to stop [$service_name]: ($result.stderr)"
        false
    }
}

# Stop Docker service
def stop-docker-service [
    service_def: record
    force: bool
] {
    let container_name = $service_def.deployment.docker.container_name

    let result = (do {
        if $force {
            docker kill $container_name
        } else {
            docker stop $container_name
        }
    } | complete)

    if $result.exit_code == 0 {
        print $"✅ Stopped Docker container: [$container_name]"
        true
    } else {
        print $"❌ Failed to stop Docker container: [$container_name] - ($result.stderr)"
        false
    }
}

# Stop Docker Compose service
def stop-docker-compose-service [
    service_def: record
] {
    let compose_config = $service_def.deployment.docker_compose
    let compose_file = ($compose_config.compose_file | str replace -a '${HOME}' $env.HOME)
    let project_name = $compose_config.project_name? | default "provisioning"

    let result = (do {
        docker compose -f $compose_file -p $project_name stop $compose_config.service_name
    } | complete)

    if $result.exit_code == 0 {
        print $"✅ Stopped Docker Compose service: ($compose_config.service_name)"
        true
    } else {
        print $"❌ Failed to stop Docker Compose service: ($compose_config.service_name) - ($result.stderr)"
        false
    }
}

# Stop Kubernetes service
def stop-kubernetes-service [
    service_def: record
    force: bool
] {
    let k8s_config = $service_def.deployment.kubernetes

    let kubeconfig = if "kubeconfig" in $k8s_config {
        ["--kubeconfig", $k8s_config.kubeconfig]
    } else {
        []
    }

    # Delete deployment
    let result = (do {
        if $force {
            kubectl ...$kubeconfig delete deployment $k8s_config.deployment_name -n $k8s_config.namespace --force --grace-period=0
        } else {
            kubectl ...$kubeconfig delete deployment $k8s_config.deployment_name -n $k8s_config.namespace
        }
    } | complete)

    if $result.exit_code == 0 {
        print $"✅ Deleted Kubernetes deployment: ($k8s_config.deployment_name)"
        true
    } else {
        print $"❌ Failed to delete Kubernetes deployment: ($k8s_config.deployment_name) - ($result.stderr)"
        false
    }
}

# Get service PID (for binary services)
export def get-service-pid [
    service_name: string
] {
    let pid_dir = (get-service-pid-dir)
    let pid_file = $"($pid_dir)/[$service_name].pid"

    if not ($pid_file | path exists) {
        return 0
    }

    let result = (do {
        open $pid_file | str trim | into int
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout | into int
    } else {
        0
    }
}

# Kill service process
export def kill-service-process [
    service_name: string
    signal: string = "TERM"
] {
    let pid = (get-service-pid $service_name)

    if $pid == 0 {
        print $"No PID found for [$service_name]"
        return false
    }

    let result = (do {
        bash -c $"kill -($signal) ($pid)"
    } | complete)

    if $result.exit_code == 0 {
        true
    } else {
        print $"Failed to send [$signal] to process [$pid]: ($result.stderr)"
        false
    }
}
