#!/bin/bash
# Deploy mcpctl stack to Portainer
# Usage: ./deploy.sh [--dry-run]

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
STACK_DIR="$SCRIPT_DIR/stack"
COMPOSE_FILE="$STACK_DIR/docker-compose.yml"
ENV_FILE="$STACK_DIR/.env"

# Portainer configuration
PORTAINER_URL="${PORTAINER_URL:-http://10.0.0.194:9000}"
PORTAINER_USER="${PORTAINER_USER:-michal}"
STACK_NAME="mcpctl"
ENDPOINT_ID="2"

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

log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }

check_files() {
    if [[ ! -f "$COMPOSE_FILE" ]]; then
        log_error "Compose file not found: $COMPOSE_FILE"
        exit 1
    fi
    if [[ ! -f "$ENV_FILE" ]]; then
        log_error "Environment file not found: $ENV_FILE"
        exit 1
    fi
    log_info "Found compose file: $COMPOSE_FILE"
    log_info "Found env file: $ENV_FILE"
}

get_password() {
    if [[ -n "$PORTAINER_PASSWORD" ]]; then
        echo "$PORTAINER_PASSWORD"
        return
    fi
    if [[ -f "$SCRIPT_DIR/.portainer_password" ]]; then
        cat "$SCRIPT_DIR/.portainer_password"
        return
    fi
    if [[ -f "$HOME/.portainer_password" ]]; then
        cat "$HOME/.portainer_password"
        return
    fi
    read -s -p "Enter Portainer password for $PORTAINER_USER: " password
    echo >&2
    echo "$password"
}

get_jwt_token() {
    local password="$1"
    log_info "Authenticating to Portainer..."

    local escaped_password
    escaped_password=$(printf '%s' "$password" | jq -Rs .)

    local response
    response=$(curl -s -X POST "$PORTAINER_URL/api/auth" \
        -H "Content-Type: application/json" \
        -d "{\"Username\":\"$PORTAINER_USER\",\"Password\":$escaped_password}")

    local token
    token=$(echo "$response" | jq -r '.jwt // empty')

    if [[ -z "$token" ]]; then
        log_error "Authentication failed: $(echo "$response" | jq -r '.message // "Unknown error"')"
        exit 1
    fi
    echo "$token"
}

parse_env_to_json() {
    local env_file="$1"
    local json_array="["
    local first=true

    while IFS= read -r line || [[ -n "$line" ]]; do
        [[ "$line" =~ ^#.*$ ]] && continue
        [[ -z "$line" ]] && continue

        local name="${line%%=*}"
        local value="${line#*=}"
        [[ "$name" == "$line" ]] && continue

        if [[ "$first" == "true" ]]; then
            first=false
        else
            json_array+=","
        fi

        value=$(echo "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
        json_array+="{\"name\":\"$name\",\"value\":\"$value\"}"
    done < "$env_file"

    json_array+="]"
    echo "$json_array"
}

# Find existing stack by name
find_stack_id() {
    local token="$1"
    local response
    response=$(curl -s -X GET "$PORTAINER_URL/api/stacks" \
        -H "Authorization: Bearer $token")

    echo "$response" | jq -r --arg name "$STACK_NAME" \
        '.[] | select(.Name == $name) | .Id // empty'
}

get_stack_info() {
    local token="$1"
    local stack_id="$2"
    curl -s -X GET "$PORTAINER_URL/api/stacks/$stack_id" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json"
}

get_stack_file() {
    local token="$1"
    local stack_id="$2"
    local response
    response=$(curl -s -X GET "$PORTAINER_URL/api/stacks/$stack_id/file" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json")

    if echo "$response" | jq -e '.StackFileContent' > /dev/null 2>&1; then
        echo "$response" | jq -r '.StackFileContent'
    else
        echo "# Could not retrieve current compose file"
    fi
}

show_diff() {
    local token="$1"
    local stack_id="$2"
    local env_json="$3"

    log_info "Fetching current state from Portainer..."

    local current_compose
    current_compose=$(get_stack_file "$token" "$stack_id")

    local current_env
    local stack_info
    stack_info=$(get_stack_info "$token" "$stack_id")
    current_env=$(echo "$stack_info" | jq -r 'if .Env then .Env[] | "\(.name)=\(.value)" else empty end' 2>/dev/null | sort)

    local new_env
    new_env=$(echo "$env_json" | jq -r '.[] | "\(.name)=\(.value)"' | sort)

    local tmp_dir
    tmp_dir=$(mktemp -d)

    echo "$current_compose" > "$tmp_dir/current_compose.yml"
    cat "$COMPOSE_FILE" > "$tmp_dir/new_compose.yml"
    echo "$current_env" > "$tmp_dir/current_env.txt"
    echo "$new_env" > "$tmp_dir/new_env.txt"

    echo ""
    echo "=== ENVIRONMENT VARIABLES DIFF ==="
    echo ""

    if diff -u "$tmp_dir/current_env.txt" "$tmp_dir/new_env.txt" > "$tmp_dir/env_diff.txt" 2>&1; then
        echo -e "${GREEN}No changes in environment variables${NC}"
    else
        while IFS= read -r line; do
            if [[ "$line" == ---* ]] || [[ "$line" == +++* ]] || [[ "$line" == @@* ]]; then
                echo -e "${YELLOW}$line${NC}"
            elif [[ "$line" == -* ]]; then
                echo -e "${RED}$line${NC}"
            elif [[ "$line" == +* ]]; then
                echo -e "${GREEN}$line${NC}"
            else
                echo "$line"
            fi
        done < "$tmp_dir/env_diff.txt"
    fi

    echo ""
    echo "=== COMPOSE FILE DIFF ==="
    echo ""

    if diff -u "$tmp_dir/current_compose.yml" "$tmp_dir/new_compose.yml" > "$tmp_dir/compose_diff.txt" 2>&1; then
        echo -e "${GREEN}No changes in compose file${NC}"
    else
        while IFS= read -r line; do
            if [[ "$line" == ---* ]] || [[ "$line" == +++* ]] || [[ "$line" == @@* ]]; then
                echo -e "${YELLOW}$line${NC}"
            elif [[ "$line" == -* ]]; then
                echo -e "${RED}$line${NC}"
            elif [[ "$line" == +* ]]; then
                echo -e "${GREEN}$line${NC}"
            else
                echo "$line"
            fi
        done < "$tmp_dir/compose_diff.txt"
    fi

    rm -rf "$tmp_dir"
}

create_stack() {
    local token="$1"
    local env_json="$2"

    local compose_content
    compose_content=$(cat "$COMPOSE_FILE")

    local compose_escaped
    compose_escaped=$(echo "$compose_content" | jq -Rs .)

    log_info "Creating new stack '$STACK_NAME'..."

    local payload
    payload=$(jq -n \
        --arg name "$STACK_NAME" \
        --argjson env "$env_json" \
        --argjson stackFileContent "$compose_escaped" \
        '{
            "name": $name,
            "env": $env,
            "stackFileContent": $stackFileContent
        }')

    local response
    response=$(curl -s -X POST "$PORTAINER_URL/api/stacks?type=2&method=string&endpointId=$ENDPOINT_ID" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json" \
        -d "$payload")

    local error_msg
    error_msg=$(echo "$response" | jq -r '.message // empty')

    if [[ -n "$error_msg" ]]; then
        log_error "Stack creation failed: $error_msg"
        echo "$response" | jq .
        exit 1
    fi

    local new_id
    new_id=$(echo "$response" | jq -r '.Id')
    log_info "Stack created successfully! (ID: $new_id)"
    echo "$response" | jq '{Id, Name, Status, CreationDate}'
}

update_stack() {
    local token="$1"
    local stack_id="$2"
    local dry_run="$3"

    local compose_content
    compose_content=$(cat "$COMPOSE_FILE")

    local env_json
    env_json=$(parse_env_to_json "$ENV_FILE")

    if [[ "$dry_run" == "true" ]]; then
        log_warn "DRY RUN - Not actually deploying"
        show_diff "$token" "$stack_id" "$env_json"
        echo ""
        log_warn "DRY RUN complete - no changes made"
        log_info "Run without --dry-run to apply these changes"
        return 0
    fi

    local env_count
    env_count=$(echo "$env_json" | jq 'length')
    log_info "Deploying $env_count environment variables"
    log_info "Updating stack '$STACK_NAME' (ID: $stack_id)..."

    local compose_escaped
    compose_escaped=$(echo "$compose_content" | jq -Rs .)

    local payload
    payload=$(jq -n \
        --argjson env "$env_json" \
        --argjson stackFileContent "$compose_escaped" \
        '{
            "env": $env,
            "stackFileContent": $stackFileContent,
            "prune": true,
            "pullImage": true
        }')

    local response
    response=$(curl -s -X PUT "$PORTAINER_URL/api/stacks/$stack_id?endpointId=$ENDPOINT_ID" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json" \
        -d "$payload")

    local error_msg
    error_msg=$(echo "$response" | jq -r '.message // empty')

    if [[ -n "$error_msg" ]]; then
        log_error "Deployment failed: $error_msg"
        echo "$response" | jq .
        exit 1
    fi

    log_info "Stack updated successfully!"
    echo "$response" | jq '{Id, Name, Status, CreationDate, UpdateDate}'
}

main() {
    local dry_run=false

    while [[ $# -gt 0 ]]; do
        case $1 in
            --dry-run)
                dry_run=true
                shift
                ;;
            --help|-h)
                echo "Usage: $0 [--dry-run]"
                echo ""
                echo "Deploy mcpctl stack to Portainer"
                echo ""
                echo "Options:"
                echo "  --dry-run    Show what would be deployed without actually deploying"
                echo "  --help       Show this help message"
                echo ""
                echo "Environment variables:"
                echo "  PORTAINER_URL       Portainer URL (default: http://10.0.0.194:9000)"
                echo "  PORTAINER_USER      Portainer username (default: michal)"
                echo "  PORTAINER_PASSWORD  Portainer password (or store in ~/.portainer_password)"
                exit 0
                ;;
            *)
                log_error "Unknown option: $1"
                exit 1
                ;;
        esac
    done

    echo "========================================"
    echo "  mcpctl Stack Deployment"
    echo "========================================"
    echo ""

    check_files

    local password
    password=$(get_password)

    local token
    token=$(get_jwt_token "$password")
    log_info "Authentication successful"

    # Find or create stack
    local stack_id
    stack_id=$(find_stack_id "$token")

    if [[ -z "$stack_id" ]]; then
        if [[ "$dry_run" == "true" ]]; then
            log_warn "Stack '$STACK_NAME' does not exist yet"
            log_info "A real deploy would create it"
            return 0
        fi

        log_info "Stack '$STACK_NAME' not found, creating..."
        local env_json
        env_json=$(parse_env_to_json "$ENV_FILE")
        create_stack "$token" "$env_json"
    else
        local stack_info
        stack_info=$(get_stack_info "$token" "$stack_id")
        local status_code
        status_code=$(echo "$stack_info" | jq -r '.Status // 0')
        local status_text="Unknown"
        case "$status_code" in
            1) status_text="Active" ;;
            2) status_text="Inactive" ;;
        esac
        log_info "Current stack status: $status_text (ID: $stack_id, Env vars: $(echo "$stack_info" | jq '.Env | length'))"

        echo ""
        update_stack "$token" "$stack_id" "$dry_run"
    fi

    echo ""
    log_info "Done!"

    if [[ "$dry_run" == "false" ]]; then
        log_info "Check Portainer UI to verify containers are running"
        log_info "URL: $PORTAINER_URL/#!/$ENDPOINT_ID/docker/stacks/$STACK_NAME"
    fi
}

main "$@"
