#!/bin/bash
set -euo pipefail # -e: exit on error, -u: exit on unset var, -o pipefail: catch errors in pipes

TIMESTAMP=$(date +%Y%m%d-%H%M%S)

get_deploy_env_file() {
  branch="$1"
  echo "/srv/jasonpoage.com/${branch}.env"
}

get_deploy_path() {
  branch="$1"
  echo "/srv/jasonpoage.com/expressjs-blog-${branch}"
}

get_log_file_path() {
  echo "/srv/jasonpoage.com/logs/receive-${TIMESTAMP}.log"
}

copy_env_file() {
  local branch="$1"
  # Copy the .env file for the test environment
  cp "$(get_deploy_env_file "$branch")" "$tmpdir/.env" || {
    echo "Error: Failed to copy .env file for test environment."
    return 1
  }
}

clone_branch() {
  local branch="$1"
  local tmpdir="$2"
  # This creates a proper working tree at $tmpdir, which is essential for submodules.
  git clone --branch "$branch" "$GIT_DIR" "$tmpdir" || {
    echo "Error: Failed to clone main application for tests to $tmpdir"
    return 1
  }
}

# Function to wait for a service to become available
_wait_for_service() {
  local url="$1"
  local timeout=30 # seconds
  local start_time
  start_time=$(date +%s)
  echo "Waiting for service at $url to become available (timeout: ${timeout}s)..."
  while :; do
    if curl --silent --fail "$url" >/dev/null; then
      echo "Service at $url is up!"
      return 0
    fi
    current_time=$(date +%s)
    if ((current_time - start_time >= timeout)); then
      echo "Error: Service at $url did not become available within ${timeout}s."
      return 1
    fi
    sleep 1
  done
}

quick_deploy_expressjs_blog() {
    local branch="$1"
    local test_dir="$2"
    local deploy_path="$3"
    local envfile="$4"
    echo "Moving tested deployment from '$test_dir' to '$deploy_path'..."
    [[ -d "$deploy_path" ]] && rm -rf "$deploy_path"
    mv "$test_dir" "$deploy_path"
    ln -f "$envfile" "$deploy_path/.env"

    systemctl --user restart express-blog@"$branch".service
}

# Smart dependency installation (only if package files changed)
get_dependencies() {
  local oldrev="$1"
  local newrev="$2"
  local install_required=true
  if [[ -n "$oldrev" && "$oldrev" != "0000000000000000000000000000000000000000" ]]; then
      if ! git --git-dir="$GIT_DIR" diff-tree --name-only -r "$oldrev..$newrev" | grep -qE "(package\.json|yarn\.lock)$"; then
          echo "No changes detected in package.json or yarn.lock. Skipping yarn install."
          install_required=false
      else
          echo "Changes detected in package.json or yarn.lock. Running yarn install."
      fi
  else
      echo "Running yarn install (initial deployment)."
  fi

  if [[ "$install_required" == "true" ]]; then
      yarn
  fi
}

# Single function to handle all repo and submodule updates (force-push safe)
update_repo_and_submodules() {
    local branch="$1"
    local deploy_path="$2"
    
    echo "Updating repository and submodules (force-push safe)..."
    
    cd "$deploy_path" || {
        echo "Error: Could not change directory to $deploy_path"
        return 1
    }
    SAVED_GIT_DIR="$GIT_DIR"
    unset GIT_DIR

    git remote show
    git branch -l
    # Update main repository - always fetch and hard reset (handles force pushes)
    echo "Fetching and updating main repository..."
    git fetch origin || {
        echo "Error: Failed to fetch from origin"
        return 1
    }
    
    git reset --hard "origin/$branch" || {
        echo "Error: Failed to reset to origin/$branch"
        return 1
    }
    
    # Update submodules - handle force pushes by resetting each submodule
    echo "Updating submodules..."
    git  submodule update --init --recursive --force || {
      echo "Standard submodule update failed, cleaning and retrying..."
      return 1
    }
    
    echo "Repository and submodules updated successfully"

    export GIT_DIR="$SAVED_GIT_DIR"
    unset SAVED_GIT_DIR
}

# Updated conservative deployment function using the unified update
conservative_deploy_expressjs_blog() {
    local branch="$1"
    local oldrev="${2:-}"
    local newrev="${3:-}"
    local deploy_path="${4:-}"
    local envfile="${5:-}"

    echo "Using preserving deployment method for production environment..."

    set -x

    # Initialize the deployment directory if it doesn't exist
    if [[ ! -d "$deploy_path" ]]; then
        echo "Creating deployment directory..."
        git clone --branch "$branch" "$GIT_DIR" "$deploy_path" || {
            echo "Error: Failed to create deployment directory"
            return 1
        }
    fi

    # Single unified update function handles everything
    update_repo_and_submodules "$branch" "$deploy_path" "$GIT_DIR" || return 1
    
    cd "$deploy_path" || {
        echo "Error: Could not change directory to $deploy_path"
        return 1
    }
    
    ln -f "$envfile" "$deploy_path/.env" || return 1

    get_dependencies "$oldrev" "$newrev" || return 1

    yarn combine:css "$oldrev" "$newrev" "$GIT_DIR" || return 1

    systemctl --user restart express-blog@"$branch".service

    set +x
}

deploy_expressjs_blog() {
    local branch="$1"
    local test_dir="$2"
    local oldrev="${3:-}"
    local newrev="${4:-}"

    # Define deployment path and env file path
    local deploy_path envfile
    deploy_path=$(get_deploy_path "$branch")
    envfile=$(get_deploy_env_file "$branch")


    case "$branch" in
      "testing")
          quick_deploy_expressjs_blog "$branch" "$test_dir" "$deploy_path" "$envfile"
          return 0
        ;;
      "staging")
          # For production/main, use the preserving method (keep logs, update in place)
          conservative_deploy_expressjs_blog "$branch" "$oldrev" "$newrev" "$deploy_path" "$envfile"
          return 0
        ;;
      "main")
        conservative_deploy_expressjs_blog "$branch" "$oldrev" "$newrev" "$deploy_path" "$envfile"
        # echo "Deployment available at: $deploy_path"
        ;;

    esac
}


run_postreceive_tests() {
  local branch="$1"
  local tmpdir pidfile logfile
  tmpdir="$2"
  pidfile="$tmpdir/test.pid"
  logfile=$(get_log_file_path)
  # Trap to ensure cleanup even if script exits unexpectedly
  trap "kill \"$(cat "$pidfile" 2>/dev/null)\" 2>/dev/null || true; rm -f \"$pidfile\"" EXIT


  echo "Running post-receive tests for branch '$branch' in temporary environment."

  clone_branch "$branch" "$tmpdir" || return 1

  cd "$tmpdir" || {
    echo "Error: Could not change directory to $tmpdir"
    return 1
  }

  copy_env_file "$branch" || return 1

  initialize_submodules "$tmpdir" || return 1

  export TEST_PORT=4123
  export TEST_SCHEMA=http
  export NODE_ENV=testing

  echo "Skipping tests"
  return 0

  echo "Running build scripts..."
  yarn
  combine_css || return 1

  echo "Starting application for tests..."
  nohup yarn start >>"$logfile" 2>&1 &
  echo $! >"$pidfile"

  set +x
  wait_for_service "$logfile"
  set -x

  echo "Running tests..."
  run_tests "$branch" "$pidfile" "$logfile" || return 1

  kill "$(cat "$pidfile")" 2>/dev/null || true
  unset TEST_PORT TEST_SCHEMA NODE_ENV

  echo "Tests passed for branch '$branch' in temporary environment."
  return 0
}

initialize_submodules() {
  local worktree="$1"
  echo "Initializing and updating submodules for test environment..."
  git --work-tree="$worktree" submodule update --init --recursive || {
    ls "$worktree" -a
    echo "Error: Failed to initialize/update submodules for test environment."
    return 1
  }
}
update_submodules() {
  local worktree="$1"
    git --work-tree="$worktree" submodule update --init --recursive || {
      echo "Error: Failed to initialize/update submodules in deployment"
      return 1
  }
}
wait_for_service() {
  local logfile="$1"
  # Wait for the application to become responsive
  if ! _wait_for_service "http://127.0.0.1:$TEST_PORT"; then
    echo "Application did not start or respond for tests. Check logs in $logfile:"
    cat "$logfile" # Display logs on failure
    return 1
  fi
}

run_tests() {
  branch="$1"
  pidfile="$2"
  logfile="$3"
  echo "Running npm tests..."
  if ! npm run test:postreceive; then
    kill "$(cat "$pidfile")" 2>/dev/null || true
    echo "Tests failed for branch '$branch'. Application logs from $logfile:"
    cat "$logfile"
    return 1
  fi
}

combine_css() {
  yarn --production=false combine:css || {
    echo "Error: yarn combine:css failed."
    return 1
  }
}

set -x
SKIP_DEPLOYMENT=false
# Main script execution loop
while read -r oldrev newrev ref; do
  tmpdir=$(mktemp -d)
  branch="${ref#refs/heads/}"
  echo "--- Processing push for branch: $branch (from $oldrev to $newrev) ---"
  if run_postreceive_tests "$branch" "$tmpdir"; then
    echo "Tests passed for $branch. Proceeding with deployment."
    deploy_expressjs_blog "$branch" "$tmpdir"
    echo "Deployment of $branch complete."
  else
    echo "Post-receive tests failed for $branch. Deployment aborted."
    exit 1
  fi
done
set +x
