#!/bin/bash
# Home Lab Configuration Sync Script
# Syncs Docker Compose and VM configs between local git repo and remote hosts

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOSTS_FILE="$SCRIPT_DIR/hosts.yml"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Helper functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

usage() {
    cat << EOF
Usage: $(basename "$0") <command> [host] [service]

Commands:
  pull [host]              Pull configs from remote hosts to local repo
  push [host]              Push configs from local repo to remote hosts (no restart)
  diff [host]              Show differences between local and remote configs
  deploy <host> <service>  Push config and restart specific service
  status                   Show sync status for all hosts
  list                     List all configured hosts and services

Examples:
  $(basename "$0") pull                    # Pull from all hosts
  $(basename "$0") pull ubuntu-manticore   # Pull from specific host
  $(basename "$0") diff discord-bots       # Show diffs for host
  $(basename "$0") deploy sba-bots paper-dynasty  # Deploy and restart service

EOF
    exit 1
}

# Parse hosts.yml using simple bash (no yq dependency)
get_hosts() {
    grep -E "^  [a-z]" "$HOSTS_FILE" | grep -v "^  #" | sed 's/://g' | awk '{print $1}'
}

get_host_property() {
    local host="$1"
    local property="$2"
    # Simple YAML parsing - works for our flat structure
    sed -n "/^  $host:/,/^  [a-z]/p" "$HOSTS_FILE" | grep "    $property:" | head -1 | sed 's/.*: *//' | tr -d '"'
}

get_host_type() {
    get_host_property "$1" "type"
}

get_ssh_alias() {
    get_host_property "$1" "ssh_alias"
}

get_docker_path() {
    local host="$1"
    sed -n "/^  $host:/,/^  [a-z]/p" "$HOSTS_FILE" | grep "docker-compose:" | head -1 | sed 's/.*: *//' | tr -d '"'
}

# Check if host is reachable
check_host() {
    local host="$1"
    local host_type
    host_type=$(get_host_type "$host")

    if [[ "$host_type" == "local" ]]; then
        return 0
    fi

    local ssh_alias
    ssh_alias=$(get_ssh_alias "$host")

    if ssh -o ConnectTimeout=3 -o BatchMode=yes "$ssh_alias" "echo ok" &>/dev/null; then
        return 0
    else
        return 1
    fi
}

# Pull configs from a single host
pull_host() {
    local host="$1"
    local host_type
    host_type=$(get_host_type "$host")
    local local_dir="$SCRIPT_DIR/$host"

    log_info "Pulling configs from $host..."

    if [[ "$host_type" == "local" ]]; then
        log_warn "Skipping local host $host (no pull needed)"
        return 0
    fi

    if ! check_host "$host"; then
        log_error "Cannot connect to $host"
        return 1
    fi

    local ssh_alias
    ssh_alias=$(get_ssh_alias "$host")

    if [[ "$host_type" == "proxmox" ]]; then
        # Pull LXC configs
        mkdir -p "$local_dir/lxc"
        log_info "  Pulling LXC configs..."
        rsync -av --delete "$ssh_alias:/etc/pve/nodes/proxmox/lxc/" "$local_dir/lxc/" 2>/dev/null || true

        # Pull QEMU configs
        mkdir -p "$local_dir/qemu"
        log_info "  Pulling QEMU/VM configs..."
        rsync -av --delete "$ssh_alias:/etc/pve/nodes/proxmox/qemu-server/" "$local_dir/qemu/" 2>/dev/null || true

    elif [[ "$host_type" == "docker" ]]; then
        local remote_path
        remote_path=$(get_docker_path "$host")

        if [[ -z "$remote_path" ]]; then
            log_warn "  No docker-compose path configured for $host"
            return 0
        fi

        mkdir -p "$local_dir/docker-compose"
        log_info "  Pulling Docker Compose configs from $remote_path..."

        # Find and sync ONLY docker-compose files (not application data)
        ssh "$ssh_alias" "find $remote_path -maxdepth 2 \( -name 'docker-compose*.yml' -o -name 'compose.yml' \) 2>/dev/null" | while read -r compose_file; do
            local service_dir
            service_dir=$(dirname "$compose_file")
            local service_name
            service_name=$(basename "$service_dir")

            mkdir -p "$local_dir/docker-compose/$service_name"

            # ONLY sync compose files and .env.example - nothing else!
            # Explicitly copy just the files we want, not directories
            scp "$ssh_alias:$service_dir/docker-compose*.yml" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
            scp "$ssh_alias:$service_dir/compose.yml" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
            scp "$ssh_alias:$service_dir/.env.example" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
        done
    fi

    log_success "Pulled configs from $host"
}

# Push configs to a single host
push_host() {
    local host="$1"
    local host_type
    host_type=$(get_host_type "$host")
    local local_dir="$SCRIPT_DIR/$host"

    log_info "Pushing configs to $host..."

    if [[ "$host_type" == "local" ]]; then
        log_warn "Skipping local host $host (no push needed)"
        return 0
    fi

    if [[ ! -d "$local_dir" ]]; then
        log_error "No local configs found for $host"
        return 1
    fi

    if ! check_host "$host"; then
        log_error "Cannot connect to $host"
        return 1
    fi

    local ssh_alias
    ssh_alias=$(get_ssh_alias "$host")

    if [[ "$host_type" == "proxmox" ]]; then
        log_warn "Pushing Proxmox configs requires manual review - skipping for safety"
        log_info "  Use 'diff $host' to review changes first"
        return 0

    elif [[ "$host_type" == "docker" ]]; then
        local remote_path
        remote_path=$(get_docker_path "$host")

        if [[ ! -d "$local_dir/docker-compose" ]]; then
            log_warn "  No docker-compose configs to push for $host"
            return 0
        fi

        for service_dir in "$local_dir/docker-compose"/*/; do
            local service_name
            service_name=$(basename "$service_dir")
            local remote_service_path="$remote_path/$service_name"

            log_info "  Pushing $service_name..."
            rsync -av --dry-run \
                --include='docker-compose*.yml' \
                --include='compose.yml' \
                --include='*.conf' \
                --exclude='*' \
                "$service_dir" "$ssh_alias:$remote_service_path/" 2>/dev/null

            # Actual push (remove --dry-run for real push)
            rsync -av \
                --include='docker-compose*.yml' \
                --include='compose.yml' \
                --include='*.conf' \
                --exclude='*' \
                "$service_dir" "$ssh_alias:$remote_service_path/" 2>/dev/null || true
        done
    fi

    log_success "Pushed configs to $host (services NOT restarted)"
}

# Show diff between local and remote
diff_host() {
    local host="$1"
    local host_type
    host_type=$(get_host_type "$host")
    local local_dir="$SCRIPT_DIR/$host"

    log_info "Comparing configs for $host..."

    if [[ "$host_type" == "local" ]]; then
        log_warn "Skipping local host $host"
        return 0
    fi

    if ! check_host "$host"; then
        log_error "Cannot connect to $host"
        return 1
    fi

    local ssh_alias
    ssh_alias=$(get_ssh_alias "$host")
    local temp_dir
    temp_dir=$(mktemp -d)

    # Pull current remote state to temp
    if [[ "$host_type" == "proxmox" ]]; then
        mkdir -p "$temp_dir/lxc" "$temp_dir/qemu"
        rsync -a "$ssh_alias:/etc/pve/nodes/proxmox/lxc/" "$temp_dir/lxc/" 2>/dev/null || true
        rsync -a "$ssh_alias:/etc/pve/nodes/proxmox/qemu-server/" "$temp_dir/qemu/" 2>/dev/null || true

        echo ""
        echo "=== LXC Config Differences ==="
        diff -rq "$local_dir/lxc" "$temp_dir/lxc" 2>/dev/null || true

        echo ""
        echo "=== QEMU Config Differences ==="
        diff -rq "$local_dir/qemu" "$temp_dir/qemu" 2>/dev/null || true

    elif [[ "$host_type" == "docker" ]]; then
        local remote_path
        remote_path=$(get_docker_path "$host")

        for service_dir in "$local_dir/docker-compose"/*/; do
            local service_name
            service_name=$(basename "$service_dir")
            local remote_service_path="$remote_path/$service_name"

            mkdir -p "$temp_dir/$service_name"
            rsync -a \
                --include='docker-compose*.yml' \
                --include='compose.yml' \
                --exclude='*' \
                "$ssh_alias:$remote_service_path/" "$temp_dir/$service_name/" 2>/dev/null || true

            echo ""
            echo "=== $service_name ==="
            diff -u "$temp_dir/$service_name/docker-compose.yml" "$service_dir/docker-compose.yml" 2>/dev/null || echo "(no differences or file missing)"
        done
    fi

    rm -rf "$temp_dir"
}

# Deploy a specific service (push + restart)
deploy_service() {
    local host="$1"
    local service="$2"
    local host_type
    host_type=$(get_host_type "$host")

    if [[ "$host_type" != "docker" ]]; then
        log_error "Deploy only works for docker hosts"
        return 1
    fi

    local ssh_alias
    ssh_alias=$(get_ssh_alias "$host")
    local remote_path
    remote_path=$(get_docker_path "$host")
    local local_dir="$SCRIPT_DIR/$host/docker-compose/$service"
    local remote_service_path="$remote_path/$service"

    if [[ ! -d "$local_dir" ]]; then
        log_error "No local config found for $service on $host"
        return 1
    fi

    if ! check_host "$host"; then
        log_error "Cannot connect to $host"
        return 1
    fi

    log_info "Deploying $service to $host..."

    # Push the config
    rsync -av \
        --include='docker-compose*.yml' \
        --include='compose.yml' \
        --include='*.conf' \
        --exclude='*' \
        "$local_dir/" "$ssh_alias:$remote_service_path/"

    # Restart the service
    log_info "Restarting $service..."
    ssh "$ssh_alias" "cd $remote_service_path && docker compose down && docker compose up -d"

    log_success "Deployed and restarted $service on $host"
}

# Show status of all hosts
show_status() {
    echo ""
    echo "Home Lab Configuration Status"
    echo "=============================="
    echo ""

    for host in $(get_hosts); do
        local host_type
        host_type=$(get_host_type "$host")
        local status_icon

        if [[ "$host_type" == "local" ]]; then
            status_icon="${GREEN}●${NC}"
            status_text="local"
        elif check_host "$host"; then
            status_icon="${GREEN}●${NC}"
            status_text="online"
        else
            status_icon="${RED}●${NC}"
            status_text="offline"
        fi

        local local_dir="$SCRIPT_DIR/$host"
        local config_count=0
        if [[ -d "$local_dir" ]]; then
            config_count=$(find "$local_dir" -name "*.yml" -o -name "*.conf" 2>/dev/null | wc -l)
        fi

        printf "  %b %-20s %-10s %-8s %d configs tracked\n" "$status_icon" "$host" "($host_type)" "$status_text" "$config_count"
    done
    echo ""
}

# List all hosts and services
list_hosts() {
    echo ""
    echo "Configured Hosts and Services"
    echo "=============================="

    for host in $(get_hosts); do
        local host_type
        host_type=$(get_host_type "$host")
        local description
        description=$(get_host_property "$host" "description")

        echo ""
        echo -e "${BLUE}$host${NC} ($host_type)"
        echo "  $description"

        local local_dir="$SCRIPT_DIR/$host/docker-compose"
        if [[ -d "$local_dir" ]]; then
            echo "  Services:"
            for service in "$local_dir"/*/; do
                if [[ -d "$service" ]]; then
                    echo "    - $(basename "$service")"
                fi
            done
        fi
    done
    echo ""
}

# Main command dispatcher
main() {
    if [[ $# -lt 1 ]]; then
        usage
    fi

    local command="$1"
    shift

    case "$command" in
        pull)
            if [[ $# -ge 1 ]]; then
                pull_host "$1"
            else
                for host in $(get_hosts); do
                    pull_host "$host" || true
                done
            fi
            ;;
        push)
            if [[ $# -ge 1 ]]; then
                push_host "$1"
            else
                for host in $(get_hosts); do
                    push_host "$host" || true
                done
            fi
            ;;
        diff)
            if [[ $# -ge 1 ]]; then
                diff_host "$1"
            else
                for host in $(get_hosts); do
                    diff_host "$host" || true
                done
            fi
            ;;
        deploy)
            if [[ $# -lt 2 ]]; then
                log_error "Deploy requires host and service arguments"
                usage
            fi
            deploy_service "$1" "$2"
            ;;
        status)
            show_status
            ;;
        list)
            list_hosts
            ;;
        *)
            log_error "Unknown command: $command"
            usage
            ;;
    esac
}

main "$@"
