#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

REPO_ROOT="$SCRIPT_DIR"
REDMINE_ROOT="/usr/share/redmine"
BACKUP_ROOT="/root/redmine-plugin-backups"

REDMINE_URL="${REDMINE_URL:-}"
REDMINE_API_KEY="${REDMINE_API_KEY:-${REDMNINE_API_KEY:-}}"
HELPDESK_ISSUE_ID="${HELPDESK_ISSUE_ID:-}"
NON_HELPDESK_ISSUE_ID="${NON_HELPDESK_ISSUE_ID:-}"

SKIP_CONTACTS=0
SKIP_HELPDESK=0
SKIP_OUTBOX=0

ACTION="deploy"
ROLLBACK_DIR=""
APPLY=0
APPLY_SET=0
PRINT_CHANGE_MAP=0

PLUGINS=(redmine_contacts redmine_contacts_helpdesk redmine_event_outbox)
SELECTED_PLUGINS=()
BACKUP_DIR=""
ROLLBACK_RUNNING=0

usage() {
  cat <<'EOF'
Usage:
  ./deploy_redmine_prod_patches.sh [options]

Defaults to dry-run deploy mode.

Modes:
  --apply                  Execute actions (default is dry-run)
  --dry-run                Print actions only
  --rollback <backup_dir>  Restore plugins from a prior backup directory

Core options:
  --repo-root <path>       Local repo root (default: script directory)
  --redmine-root <path>    Production Redmine root (default: /usr/share/redmine)
  --backup-root <path>     Backup root for deploy mode (default: /root/redmine-plugin-backups)

Verification options:
  --redmine-url <url>             Redmine base URL for API checks
  --api-key <key>                 Redmine API key for API checks
  --helpdesk-issue-id <id>        Known Helpdesk issue id for include=helpdesk verification
  --non-helpdesk-issue-id <id>    Known non-Helpdesk issue id for include=helpdesk verification

Selection options:
  --skip-contacts
  --skip-helpdesk
  --skip-outbox

Informational:
  --print-change-map       Show patch groups and key files, then exit
  -h, --help               Show this help

Examples:
  ./deploy_redmine_prod_patches.sh --dry-run
  ./deploy_redmine_prod_patches.sh --apply --redmine-url https://redmine.example.com --api-key ... --helpdesk-issue-id 39779 --non-helpdesk-issue-id 12345
  ./deploy_redmine_prod_patches.sh --rollback /root/redmine-plugin-backups/prod-plugin-rollout-20260506T120000Z
EOF
}

log() {
  printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*"
}

die() {
  printf 'error: %s\n' "$*" >&2
  exit 1
}

print_change_map() {
  cat <<'EOF'
Patch Groups -> Key Files

1) Helpdesk API include patch (semantic-index dependency)
   - plugins/redmine_contacts_helpdesk/app/views/issues/show.api.rsb
   - plugins/redmine_contacts_helpdesk/lib/redmine_helpdesk/issue_api_serializer.rb
   - plugins/redmine_contacts_helpdesk/lib/redmine_helpdesk.rb

2) Helpdesk search routes/controller in local helpdesk fork
   - plugins/redmine_contacts_helpdesk/app/controllers/helpdesk_search_controller.rb
   - plugins/redmine_contacts_helpdesk/config/routes.rb
   - plugins/redmine_contacts_helpdesk/init.rb

3) POP3 compatibility fix in contacts fork
   - plugins/redmine_contacts/lib/redmine_contacts/utils/check_mail.rb

4) Event outbox + Helpdesk-related hooks
   - plugins/redmine_event_outbox/db/migrate/001_create_event_outbox_events.rb
   - plugins/redmine_event_outbox/lib/redmine_event_outbox.rb
   - plugins/redmine_event_outbox/lib/redmine_event_outbox/patches/helpdesk_ticket_patch.rb
   - plugins/redmine_event_outbox/lib/redmine_event_outbox/patches/journal_message_patch.rb

Operational note:
   This script deploys full plugin directories to match the post-import workflow:
   - plugins/redmine_contacts/
   - plugins/redmine_contacts_helpdesk/
   - plugins/redmine_event_outbox/
EOF
}

cmd_string() {
  local out=""
  local arg
  for arg in "$@"; do
    out+=" $(printf '%q' "$arg")"
  done
  printf '%s' "${out# }"
}

run_cmd() {
  log "+ $(cmd_string "$@")"
  if [[ "$APPLY" -eq 1 ]]; then
    "$@"
  fi
}

require_path() {
  local path="$1"
  [[ -e "$path" ]] || die "missing required path: $path"
}

require_dir() {
  local path="$1"
  [[ -d "$path" ]] || die "missing required directory: $path"
}

build_selected_plugins() {
  SELECTED_PLUGINS=()
  if [[ "$SKIP_CONTACTS" -eq 0 ]]; then
    SELECTED_PLUGINS+=("redmine_contacts")
  fi
  if [[ "$SKIP_HELPDESK" -eq 0 ]]; then
    SELECTED_PLUGINS+=("redmine_contacts_helpdesk")
  fi
  if [[ "$SKIP_OUTBOX" -eq 0 ]]; then
    SELECTED_PLUGINS+=("redmine_event_outbox")
  fi

  if [[ "${#SELECTED_PLUGINS[@]}" -eq 0 ]]; then
    die "nothing selected; remove skip flags or pick at least one plugin"
  fi
}

backup_plugin() {
  local plugin="$1"
  local src="$REDMINE_ROOT/plugins/$plugin"
  local dst="$BACKUP_DIR/$plugin"
  local absent_marker="$BACKUP_DIR/.${plugin}.absent"

  if [[ -d "$src" ]]; then
    run_cmd mkdir -p "$dst"
    run_cmd rsync -a "$src/" "$dst/"
  else
    run_cmd mkdir -p "$BACKUP_DIR"
    run_cmd touch "$absent_marker"
  fi
}

restore_plugin() {
  local plugin="$1"
  local src="$ROLLBACK_DIR/$plugin"
  local dst="$REDMINE_ROOT/plugins/$plugin"
  local absent_marker="$ROLLBACK_DIR/.${plugin}.absent"

  if [[ -f "$absent_marker" ]]; then
    run_cmd rm -rf "$dst"
    return
  fi

  require_dir "$src"
  run_cmd mkdir -p "$dst"
  run_cmd rsync -a --delete "$src/" "$dst/"
}

deploy_plugin() {
  local plugin="$1"
  local src="$REPO_ROOT/plugins/$plugin"
  local dst="$REDMINE_ROOT/plugins/$plugin"

  require_dir "$src"
  run_cmd mkdir -p "$dst"
  run_cmd rsync -a --delete "$src/" "$dst/"
}

verify_contacts_syntax() {
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_contacts/lib/redmine_contacts/utils/check_mail.rb"
}

verify_helpdesk_syntax() {
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_contacts_helpdesk/lib/redmine_helpdesk.rb"
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_contacts_helpdesk/lib/redmine_helpdesk/issue_api_serializer.rb"
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_contacts_helpdesk/app/views/issues/show.api.rsb"
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_contacts_helpdesk/app/models/helpdesk_mailer.rb"
}

verify_outbox_syntax() {
  run_cmd ruby -c "$REDMINE_ROOT/plugins/redmine_event_outbox/lib/redmine_event_outbox.rb"
}

migrate_plugins() {
  run_cmd bash -lc "cd $(printf '%q' "$REDMINE_ROOT") && RAILS_ENV=production bundle exec rake redmine:plugins:migrate"
}

restart_passenger() {
  run_cmd touch "$REDMINE_ROOT/tmp/restart.txt"
}

verify_outbox_table() {
  if [[ "$SKIP_OUTBOX" -eq 1 ]]; then
    return
  fi
  run_cmd bash -lc "cd $(printf '%q' "$REDMINE_ROOT") && RAILS_ENV=production bundle exec ruby -e \"require './config/environment'; abort('missing event_outbox_events') unless ActiveRecord::Base.connection.table_exists?(:event_outbox_events); puts 'OK event_outbox_events'\""
}

verify_helpdesk_api() {
  if [[ "$SKIP_HELPDESK" -eq 1 ]]; then
    return
  fi

  if [[ -z "$REDMINE_URL" || -z "$REDMINE_API_KEY" ]]; then
    log "Skipping API verification (set --redmine-url and --api-key to enable)"
    return
  fi

  local tmp1 tmp2
  tmp1="$(mktemp)"
  tmp2="$(mktemp)"

  if [[ -n "$HELPDESK_ISSUE_ID" ]]; then
    run_cmd curl -fsS -H "X-Redmine-API-Key: $REDMINE_API_KEY" "${REDMINE_URL%/}/issues/${HELPDESK_ISSUE_ID}.json?include=journals,helpdesk" -o "$tmp1"
    run_cmd grep -q '"helpdesk_ticket"' "$tmp1"
  else
    log "Skipping Helpdesk issue include check (set --helpdesk-issue-id)"
  fi

  if [[ -n "$NON_HELPDESK_ISSUE_ID" ]]; then
    run_cmd curl -fsS -H "X-Redmine-API-Key: $REDMINE_API_KEY" "${REDMINE_URL%/}/issues/${NON_HELPDESK_ISSUE_ID}.json?include=helpdesk" -o "$tmp2"
    run_cmd grep -q '"issue"' "$tmp2"
  else
    log "Skipping non-Helpdesk include check (set --non-helpdesk-issue-id)"
  fi

  if [[ "$APPLY" -eq 1 ]]; then
    rm -f "$tmp1" "$tmp2"
  else
    log "Temporary API response files (dry-run placeholders): $tmp1 $tmp2"
  fi
}

rollback_selected_plugins() {
  local plugin
  for plugin in "${SELECTED_PLUGINS[@]}"; do
    log "Restoring plugin: $plugin"
    restore_plugin "$plugin"
  done
  restart_passenger
}

on_error() {
  local line="$1"
  local rc="$2"
  if [[ "$ROLLBACK_RUNNING" -eq 1 ]]; then
    exit "$rc"
  fi
  log "Failure at line ${line} (exit ${rc})"
  if [[ "$ACTION" == "deploy" && "$APPLY" -eq 1 && -n "$BACKUP_DIR" ]]; then
    ROLLBACK_RUNNING=1
    ROLLBACK_DIR="$BACKUP_DIR"
    log "Attempting automatic rollback from: $ROLLBACK_DIR"
    rollback_selected_plugins || true
    log "Automatic rollback finished"
  fi
  exit "$rc"
}

parse_args() {
  while [[ "$#" -gt 0 ]]; do
    case "$1" in
      --apply)
        APPLY=1
        APPLY_SET=1
        ;;
      --dry-run)
        APPLY=0
        APPLY_SET=1
        ;;
      --rollback)
        ACTION="rollback"
        shift
        [[ "$#" -gt 0 ]] || die "--rollback requires a backup directory"
        ROLLBACK_DIR="$1"
        ;;
      --repo-root)
        shift
        [[ "$#" -gt 0 ]] || die "--repo-root requires a path"
        REPO_ROOT="$1"
        ;;
      --redmine-root)
        shift
        [[ "$#" -gt 0 ]] || die "--redmine-root requires a path"
        REDMINE_ROOT="$1"
        ;;
      --backup-root)
        shift
        [[ "$#" -gt 0 ]] || die "--backup-root requires a path"
        BACKUP_ROOT="$1"
        ;;
      --redmine-url)
        shift
        [[ "$#" -gt 0 ]] || die "--redmine-url requires a value"
        REDMINE_URL="$1"
        ;;
      --api-key)
        shift
        [[ "$#" -gt 0 ]] || die "--api-key requires a value"
        REDMINE_API_KEY="$1"
        ;;
      --helpdesk-issue-id)
        shift
        [[ "$#" -gt 0 ]] || die "--helpdesk-issue-id requires a value"
        HELPDESK_ISSUE_ID="$1"
        ;;
      --non-helpdesk-issue-id)
        shift
        [[ "$#" -gt 0 ]] || die "--non-helpdesk-issue-id requires a value"
        NON_HELPDESK_ISSUE_ID="$1"
        ;;
      --skip-contacts)
        SKIP_CONTACTS=1
        ;;
      --skip-helpdesk)
        SKIP_HELPDESK=1
        ;;
      --skip-outbox)
        SKIP_OUTBOX=1
        ;;
      --print-change-map)
        PRINT_CHANGE_MAP=1
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        die "unknown argument: $1"
        ;;
    esac
    shift
  done
}

main() {
  parse_args "$@"

  if [[ "$PRINT_CHANGE_MAP" -eq 1 ]]; then
    print_change_map
    exit 0
  fi

  if [[ "$ACTION" == "rollback" && "$APPLY_SET" -eq 0 ]]; then
    APPLY=1
  fi

  if [[ "$APPLY" -eq 1 && "$EUID" -ne 0 ]]; then
    die "--apply and --rollback apply mode require root"
  fi

  require_dir "$REDMINE_ROOT"
  require_dir "$REDMINE_ROOT/plugins"
  require_path "$REDMINE_ROOT/tmp"

  build_selected_plugins
  trap 'on_error ${LINENO} $?' ERR

  if [[ "$ACTION" == "rollback" ]]; then
    require_dir "$ROLLBACK_DIR"
    log "Mode: rollback ($([[ "$APPLY" -eq 1 ]] && echo apply || echo dry-run))"
    log "Redmine root: $REDMINE_ROOT"
    log "Backup source: $ROLLBACK_DIR"
    rollback_selected_plugins
    trap - ERR
    log "Rollback completed"
    exit 0
  fi

  require_dir "$REPO_ROOT"
  require_dir "$REPO_ROOT/plugins"

  BACKUP_DIR="$BACKUP_ROOT/prod-plugin-rollout-$(date -u +%Y%m%dT%H%M%SZ)"

  log "Mode: deploy ($([[ "$APPLY" -eq 1 ]] && echo apply || echo dry-run))"
  log "Repo root: $REPO_ROOT"
  log "Redmine root: $REDMINE_ROOT"
  log "Backup dir: $BACKUP_DIR"
  log "Selected plugins: ${SELECTED_PLUGINS[*]}"

  run_cmd mkdir -p "$BACKUP_DIR"

  local plugin
  for plugin in "${SELECTED_PLUGINS[@]}"; do
    log "Backing up plugin: $plugin"
    backup_plugin "$plugin"
  done

  if [[ "$SKIP_CONTACTS" -eq 0 ]]; then
    log "Deploying plugin: redmine_contacts"
    deploy_plugin "redmine_contacts"
    log "Verifying syntax: redmine_contacts"
    verify_contacts_syntax
  fi

  if [[ "$SKIP_HELPDESK" -eq 0 ]]; then
    log "Deploying plugin: redmine_contacts_helpdesk"
    deploy_plugin "redmine_contacts_helpdesk"
    log "Verifying syntax: redmine_contacts_helpdesk"
    verify_helpdesk_syntax
  fi

  if [[ "$SKIP_OUTBOX" -eq 0 ]]; then
    log "Deploying plugin: redmine_event_outbox"
    deploy_plugin "redmine_event_outbox"
    log "Verifying syntax: redmine_event_outbox"
    verify_outbox_syntax
  fi

  log "Running plugin migrations"
  migrate_plugins

  log "Restarting Passenger"
  restart_passenger

  log "Running runtime verifications"
  verify_outbox_table
  verify_helpdesk_api

  trap - ERR
  log "Deploy completed successfully"
  log "Rollback command: $(cmd_string "$0" --rollback "$BACKUP_DIR")"
}

main "$@"
