#!/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
}

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..."
    
    # Define the bare repositories
    local MAIN_APP_BARE_REPO="$GIT_DIR"
    local CONTENT_BARE_REPO="/srv/jasonpoage.com/expressjs-blog-posts.git"

    set -x

    # Update the working tree in place
    git --git-dir="$MAIN_APP_BARE_REPO" --work-tree="$deploy_path" checkout -f "$branch"

    cd "$deploy_path" || {
        echo "Error: Could not change directory to $deploy_path"
        return 1
    }
    
    ln -f "$envfile" "$deploy_path/.env"

    # Smart dependency installation (only if package files changed)
    local install_required=true
    if [[ -n "$oldrev" && "$oldrev" != "0000000000000000000000000000000000000000" ]]; then
        if ! git --git-dir="$MAIN_APP_BARE_REPO" 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

    # Smart CSS bundling (only if CSS files changed)
    if [[ -n "$oldrev" && -n "$newrev" ]]; then
        yarn combine:css "$oldrev" "$newrev" "$MAIN_APP_BARE_REPO"
    else
        yarn combine:css
    fi

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

    # Handle content repository updates
    if [[ -d "$deploy_path/content" ]]; then
        cd "$deploy_path/content" || {
            echo "Error: Could not change directory to $deploy_path/content"
            return 1
        }
        current_commit=$(git rev-parse HEAD)
        git fetch origin
        latest_commit=$(git rev-parse origin/main)

        if [[ "$current_commit" != "$latest_commit" ]]; then
            git reset --hard origin/main
            echo "Content updated to latest commit"
        else
            echo "Content already up to date"
        fi
    else
        git clone --branch main "$CONTENT_BARE_REPO" "$deploy_path/content"
    fi

    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")

    if [[ "$branch" == "staging" || "$branch" == "main" || "$branch" == "testing" ]]; then
        
        # For testing environments, use the efficient method (replace everything)
        if [[ "$branch" == "testing" ]]; then
            quick_deploy_expressjs_blog "$branch" "$test_dir" "$deploy_path" "$envfile"
            return 0
        else
            # For production/main, use the preserving method (keep logs, update in place)
            conservative_deploy_expressjs_blog "$branch" "$oldrev" "$newrev" "$deploy_path" "$envfile"
            return 0
        fi
    fi
}


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 "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 tmpdir="$1"
  echo "Initializing and updating submodules for test environment..."
  git --work-tree="$tmpdir" submodule update --init --recursive || {
    ls "$tmpdir" -a
    echo "Error: Failed to initialize/update submodules for test environment."
    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
# 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
