#!/usr/bin/env nu

# Service Dependency Resolution
# Handles dependency graph analysis and startup ordering

use manager.nu [load-service-registry get-service-definition]

# Resolve service dependencies
export def resolve-dependencies [
    service_name: string
] {
    let service_def = (get-service-definition $service_name)

    if ($service_def.dependencies | is-empty) {
        return []
    }

    # Recursively resolve dependencies - collect all unique deps
    def accumulate-deps [deps: list, all_deps: list] {
        if ($deps | is-empty) {
            return $all_deps
        }

        let first = ($deps | get 0)
        let rest = ($deps | skip 1)
        let with_first = if $first in $all_deps { $all_deps } else { $all_deps | append $first }
        let sub_deps = (resolve-dependencies $first)
        let all_sub_deps = ($sub_deps | append $rest)

        accumulate-deps $all_sub_deps $with_first
    }

    accumulate-deps $service_def.dependencies []
}

# Get dependency tree
export def get-dependency-tree [
    service_name: string
] {
    let service_def = (get-service-definition $service_name)

    if ($service_def.dependencies | is-empty) {
        return {
            service: $service_name
            dependencies: []
        }
    }

    let deps = (
        $service_def.dependencies
        | each { |dep|
            get-dependency-tree $dep
        }
    )

    {
        service: $service_name
        dependencies: $deps
    }
}

# Topological sort for dependency ordering
def topological-sort [
    services: list
    dep_map: record
] {
    # Recursive DFS helper function
    def visit [
        node: string
        dep_map: record
        visited: record
        visiting: record
        sorted: list
    ] {
        if $node in ($visiting | columns) {
            error make {
                msg: "Circular dependency detected"
                label: {
                    text: $"Cycle involving ($node)"
                    span: (metadata $node).span
                }
            }
        }

        if $node in ($visited | columns) {
            return { visited: $visited, visiting: $visiting, sorted: $sorted }
        }

        let new_visiting = ($visiting | insert $node true)

        let deps = if $node in ($dep_map | columns) {
            $dep_map | get $node
        } else {
            []
        }

        # Process dependencies recursively
        def visit-deps [deps: list, state: record] {
            if ($deps | is-empty) {
                return $state
            }
            let first_dep = ($deps | get 0)
            let rest_deps = ($deps | skip 1)
            let state_after = (visit $first_dep $dep_map $state.visited $state.visiting $state.sorted)
            visit-deps $rest_deps $state_after
        }

        let result_after_deps = (visit-deps $deps { visited: $visited, visiting: $new_visiting, sorted: $sorted })

        let final_visiting = ($result_after_deps.visiting | reject $node)
        let final_visited = ($result_after_deps.visited | insert $node true)
        let final_sorted = ($result_after_deps.sorted | append $node)

        { visited: $final_visited, visiting: $final_visiting, sorted: $final_sorted }
    }

    # Visit all nodes recursively starting with empty state
    def visit-services [services: list, state: record] {
        if ($services | is-empty) {
            return $state
        }
        let first_service = ($services | get 0)
        let rest_services = ($services | skip 1)
        let state_after = if $first_service not-in ($state.visited | columns) {
            visit $first_service $dep_map $state.visited $state.visiting $state.sorted
        } else {
            $state
        }
        visit-services $rest_services $state_after
    }

    (visit-services $services { visited: {}, visiting: {}, sorted: [] }) | get sorted
}

# Start services in dependency order
export def start-services-with-deps [
    service_names: list
] {
    # Build dependency map
    let registry = (load-service-registry)

    # Helper to build dep_map from registry entries
    def build-dep-map [entries: list, acc: record] {
        if ($entries | is-empty) {
            return $acc
        }
        let first = ($entries | get 0)
        let rest = ($entries | skip 1)
        let new_acc = ($acc | insert $first.name $first.config.dependencies)
        build-dep-map $rest $new_acc
    }

    let dep_map = (build-dep-map ($registry | transpose name config) {})

    # Helper to collect all services with their dependencies
    def collect-services [services: list, all_deps: list] {
        if ($services | is-empty) {
            return $all_deps
        }
        let first = ($services | get 0)
        let rest = ($services | skip 1)
        let with_first = if $first in $all_deps { $all_deps } else { $all_deps | append $first }
        let sub_deps = (resolve-dependencies $first)
        collect-services (($rest | append $sub_deps)) $with_first
    }

    let all_services = (collect-services $service_names [])

    # Get startup order
    let startup_order = (topological-sort $all_services $dep_map)

    print $"Starting services in order: ($startup_order | str join ' -> ')"

    # Helper to start services recursively
    def start-services [services: list, state: record] {
        if ($services | is-empty) {
            return $state
        }
        let first = ($services | get 0)
        let rest = ($services | skip 1)
        use manager.nu [is-service-running start-service]

        let new_state = if (is-service-running $first) {
            print $"✅ ($first) already running"
            {
                started: ($state.started | append $first)
                failed: $state.failed
            }
        } else {
            print $"Starting [$first]..."
            let start_result = (do {
                start-service $first
            } | complete)

            if $start_result.exit_code == 0 and ($start_result.stdout == "true" or $start_result.stdout == true) {
                print $"✅ [$first] started"
                {
                    started: ($state.started | append $first)
                    failed: $state.failed
                }
            } else {
                print $"❌ Failed to start [$first]: ($start_result.stderr)"
                {
                    started: $state.started
                    failed: ($state.failed | append $first)
                }
            }
        }
        start-services $rest $new_state
    }

    let result = (start-services $startup_order { started: [], failed: [] })

    let started = $result.started
    let failed = $result.failed

    {
        success: ($failed | is-empty)
        startup_order: $startup_order
        started: $started
        failed: $failed
        message: (if ($failed | is-empty) {
            $"Successfully started ($started | length) services"
        } else {
            $"Failed at service: ($failed | get 0)"
        })
    }
}

# Validate dependency graph (detect cycles)
export def validate-dependency-graph [] {
    let registry = (load-service-registry)

    # Helper to build dep_map from registry entries
    def build-dep-map [entries: list, acc: record] {
        if ($entries | is-empty) {
            return $acc
        }
        let first = ($entries | get 0)
        let rest = ($entries | skip 1)
        let new_acc = ($acc | insert $first.name $first.config.dependencies)
        build-dep-map $rest $new_acc
    }

    let dep_map = (build-dep-map ($registry | transpose name config) {})

    let services = ($registry | columns)

    # Try to topologically sort - will error if cycle detected
    let sort_result = (do {
        topological-sort $services $dep_map
    } | complete)

    if $sort_result.exit_code == 0 {
        {
            valid: true
            has_cycles: false
            message: "Dependency graph is valid (no cycles)"
            startup_order: ($sort_result.stdout | from json)
        }
    } else {
        {
            valid: false
            has_cycles: true
            message: $"Dependency graph has cycles: ($sort_result.stderr)"
            error: $sort_result.stderr
        }
    }
}

# Get startup order
export def get-startup-order [
    service_names: list
] {
    let registry = (load-service-registry)

    # Helper to build dep_map from registry entries
    def build-dep-map [entries: list, acc: record] {
        if ($entries | is-empty) {
            return $acc
        }
        let first = ($entries | get 0)
        let rest = ($entries | skip 1)
        let new_acc = ($acc | insert $first.name $first.config.dependencies)
        build-dep-map $rest $new_acc
    }

    let dep_map = (build-dep-map ($registry | transpose name config) {})

    # Helper to collect all services with their dependencies
    def collect-services [services: list, all_deps: list] {
        if ($services | is-empty) {
            return $all_deps
        }
        let first = ($services | get 0)
        let rest = ($services | skip 1)
        let with_first = if $first in $all_deps { $all_deps } else { $all_deps | append $first }
        let sub_deps = (resolve-dependencies $first)
        collect-services (($rest | append $sub_deps)) $with_first
    }

    let all_services = (collect-services $service_names [])

    # Also sort by start_order field
    let services_with_order = (
        $all_services
        | each { |name|
            let service_def = (get-service-definition $name)
            {
                name: $name
                start_order: $service_def.startup.start_order
            }
        }
        | sort-by start_order
        | get name
    )

    # Perform topological sort
    let sort_result = (do {
        topological-sort $services_with_order $dep_map
    } | complete)

    if $sort_result.exit_code == 0 {
        $sort_result.stdout | from json
    } else {
        # Fallback to simple order if cycle detected
        print "⚠️  Warning: Circular dependency detected, using start_order fallback"
        $services_with_order
    }
}

# Get reverse dependencies (which services depend on this one)
export def get-reverse-dependencies [
    service_name: string
] {
    let registry = (load-service-registry)

    $registry
    | transpose name config
    | where { |row|
        $service_name in $row.config.dependencies
    }
    | get name
}

# Get dependency graph visualization
export def visualize-dependency-graph [] {
    let registry = (load-service-registry)

    # Helper to format a single service's dependencies
    def format-service-deps [service: string, lines: list] {
        let service_def = (get-service-definition $service)

        let base_lines = (
            $lines
            | append $"## ($service)"
            | append $"- Type: ($service_def.type)"
            | append $"- Category: ($service_def.category)"
        )

        let with_deps = (
            if not ($service_def.dependencies | is-empty) {
                ($base_lines | append "- Dependencies:")
                | append (
                    $service_def.dependencies
                    | each { |dep| $"  - ($dep)" }
                )
            } else {
                $base_lines
            }
        )

        let reverse_deps = (get-reverse-dependencies $service)
        let with_reverse = (
            if not ($reverse_deps | is-empty) {
                ($with_deps | append "- Required by:")
                | append (
                    $reverse_deps
                    | each { |dep| $"  - ($dep)" }
                )
            } else {
                $with_deps
            }
        )

        let final = (
            if not ($service_def.conflicts | is-empty) {
                ($with_reverse | append "- Conflicts:")
                | append (
                    $service_def.conflicts
                    | each { |conflict| $"  - ($conflict)" }
                )
            } else {
                $with_reverse
            }
        )

        ($final | append "")
    }

    # Helper to format all services recursively
    def format-services [services: list, lines: list] {
        if ($services | is-empty) {
            return $lines
        }
        let first = ($services | get 0)
        let rest = ($services | skip 1)
        let new_lines = (format-service-deps $first $lines)
        format-services $rest $new_lines
    }

    let output = (
        ["# Service Dependency Graph", ""]
        | append (format-services (($registry | columns | sort)) [])
    )

    $output | str join "\n"
}

# Check if service can be stopped safely
export def can-stop-service [
    service_name: string
] {
    use manager.nu is-service-running

    let reverse_deps = (get-reverse-dependencies $service_name)

    # Check which dependent services are running
    let running_deps = (
        $reverse_deps
        | where { |dep| is-service-running $dep }
    )

    {
        service: $service_name
        can_stop: ($running_deps | is-empty)
        dependent_services: $reverse_deps
        running_dependents: $running_deps
        message: (if ($running_deps | is-empty) {
            "Service can be stopped safely"
        } else {
            $"Cannot stop: ($running_deps | length) dependent services are running"
        })
    }
}
