#!/usr/bin/env bash
# =============================================================================
# dolibarr_patch_update.sh
#
# Re-applies .php.patch files after a Dolibarr core upgrade.
#
# File convention (all three files in the same directory):
#   foo.php        — current file (overwritten by new Dolibarr after upgrade)
#   foo.php.org    — original from OLD Dolibarr (reference baseline)
#   foo.php.patch  — your customised version (what you want to keep)
#
# Works for any file type: .php, .inc.php, .js, .css, etc.
# The script discovers all *.org files and derives the base name automatically.
#
# Logic per .php.org found:
#   foo.php == foo.php.org  →  core UNCHANGED → cp foo.php.patch  foo.php
#   foo.php != foo.php.org  →  core CHANGED   → diff3 3-way merge:
#                                 clean    → write result to foo.php
#                                 conflict → write foo.php.merged  (manual review)
#
# In ALL cases before writing foo.php:  cp foo.php  foo.php.bak
#
# Usage:
#   ./dolibarr_patch_update.sh [search_root] [--dry-run]
#
#   search_root   Directory to scan recursively (default: current directory)
#   --dry-run     Show what would happen without modifying any files
# =============================================================================

# ── safety ────────────────────────────────────────────────────────────────────
set -uo pipefail
# NOTE: we intentionally do NOT use -e because diff / diff3 return non-zero
# on differences/conflicts and we handle those exit codes manually.

# ── defaults ──────────────────────────────────────────────────────────────────
SEARCH_ROOT="."
DRY_RUN=false
LOG_FILE="patch_update_$(date +%Y%m%d).log"

# ── help function ─────────────────────────────────────────────────────────────
show_help() {
  echo ""
  echo "  Usage: $0 <search_root> [--dry-run]"
  echo ""
  echo "  Re-applies .patch files after a Dolibarr core upgrade."
  echo ""
  echo "  Arguments:"
  echo "    search_root   Path to scan recursively for *.org files (required)"
  echo "    --dry-run     Show what would happen without modifying any files"
  echo "    --help, -h    Show this help message"
  echo ""
  echo "  File convention (all three files must be in the same directory):"
  echo "    foo.php         Current file (overwritten by new Dolibarr)"
  echo "    foo.php.org     Original from OLD Dolibarr  (reference baseline)"
  echo "    foo.php.patch   Your customised version      (what you want to keep)"
  echo ""
  echo "  Outcomes:"
  echo "    CLEAN   foo.php == foo.php.org  → patch applied directly"
  echo "    MERGED  foo.php != foo.php.org  → 3-way merge succeeded"
  echo "    CONFLICT                        → foo.php.merged written for manual review"
  echo "    OK      foo.php == foo.php.patch → nothing to do"
  echo "    SKIP    missing .php or .patch file"
  echo ""
  echo "  After manual conflict resolution:"
  echo "    mv foo.php.merged foo.php"
  echo "    cp foo.php foo.php.org    # update baseline for next upgrade"
  echo "    cp foo.php foo.php.patch  # update patch"
  echo ""
  echo "  Example:"
  echo "    $0 /var/www/html/dolibarr/htdocs --dry-run"
  echo "    $0 /var/www/html/dolibarr/htdocs"
  echo ""
}

# ── arg parsing ───────────────────────────────────────────────────────────────
if [[ $# -eq 0 ]]; then
  show_help
  exit 0
fi

for arg in "$@"; do
  case "$arg" in
    --dry-run) DRY_RUN=true ;;
    --help|-h)
      show_help
      exit 0
      ;;
    -*)
      echo "Unknown option: $arg  (use --help for usage)"
      exit 1
      ;;
    *)
      SEARCH_ROOT="$arg"
      ;;
  esac
done

# ── colour codes ──────────────────────────────────────────────────────────────
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

# ── dual-output logging ───────────────────────────────────────────────────────
# Console gets colours; log file gets plain text.
log() {
  local msg="$*"
  local ts
  ts="[$(date '+%Y-%m-%d %H:%M:%S')]"
  echo -e "${ts} ${msg}"
  # strip ANSI escape codes for the log file
  echo "${ts} ${msg}" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"
}

# ── dependency check ──────────────────────────────────────────────────────────
for cmd in diff diff3 cp mv find; do
  if ! command -v "$cmd" &> /dev/null; then
    echo "ERROR: required command '$cmd' not found." >&2
    exit 1
  fi
done

# ── counters ──────────────────────────────────────────────────────────────────
CNT_CLEAN=0
CNT_MERGED=0
CNT_CONFLICT=0
CNT_SKIP=0

# ── header ────────────────────────────────────────────────────────────────────
log "${BOLD}==============================${NC}"
log "${BOLD} Dolibarr Patch Update Script${NC}"
log "${BOLD}==============================${NC}"
log "Search root : ${CYAN}${SEARCH_ROOT}${NC}"
log "Dry-run     : ${YELLOW}${DRY_RUN}${NC}"
log "Log file    : ${CYAN}${LOG_FILE}${NC}"
log ""

# ── main loop ─────────────────────────────────────────────────────────────────
while IFS= read -r ORG_FILE; do

  BASE="${ORG_FILE%.org}"       # e.g. /path/to/main.inc.php
  PATCH_FILE="${BASE}.patch"    # e.g. /path/to/main.inc.php.patch
  BAK_FILE="${BASE}.bak"        # e.g. /path/to/main.inc.php.bak
  MERGED_FILE="${BASE}.merged"  # only created on conflict

  # ── sanity checks ────────────────────────────────────────────────────────
  if [[ ! -f "$BASE" ]]; then
    log "${YELLOW}SKIP${NC}   ${BASE}  — no .php file found"
    (( CNT_SKIP++ )) || true
    continue
  fi
  if [[ ! -f "$PATCH_FILE" ]]; then
    log "${YELLOW}SKIP${NC}   ${BASE}  — no .php.patch file found"
    (( CNT_SKIP++ )) || true
    continue
  fi

  # ── early exit: current file already identical to patch ──────────────────
  if diff -q "$BASE" "$PATCH_FILE" > /dev/null 2>&1; then
    log "${GREEN}OK   ${NC}  ${BASE}"
    log "       ${CYAN}→${NC} file is already identical to patch, nothing to do"
    (( CNT_SKIP++ )) || true
    continue
  fi

  # ── compare current .php with old .php.org ───────────────────────────────
  if diff -q "$BASE" "$ORG_FILE" > /dev/null 2>&1; then

    # =========================================================
    # CASE 1: core file identical to old baseline → clean apply
    # =========================================================
    log "${GREEN}CLEAN${NC}  ${BASE}"
    log "       ${CYAN}→${NC} core unchanged, applying patch directly"

    if [[ "$DRY_RUN" == false ]]; then
      cp "$BASE" "$BAK_FILE"
      cp "$PATCH_FILE" "$BASE"
    fi
    (( CNT_CLEAN++ )) || true

  else

    # =========================================================
    # CASE 2: core changed → 3-way diff3 merge
    # diff3 args:  mine=.patch  base=.org  theirs=.php(new)
    # =========================================================
    log "${CYAN}DIFF ${NC}  ${BASE}"
    log "       ${CYAN}→${NC} core changed, attempting 3-way merge…"

    if [[ "$DRY_RUN" == false ]]; then
      # Run diff3 and capture output + exit code separately
      diff3 -m "$PATCH_FILE" "$ORG_FILE" "$BASE" > "$MERGED_FILE" 2>/dev/null
      DIFF3_EXIT=$?
    else
      # Dry-run: run but discard output, only check exit code
      diff3 -m "$PATCH_FILE" "$ORG_FILE" "$BASE" > /dev/null 2>/dev/null
      DIFF3_EXIT=$?
    fi

    # diff3 exit codes: 0=no conflicts, 1=conflicts found, 2=error
    if [[ $DIFF3_EXIT -eq 0 ]]; then
      log "       ${GREEN}└─ clean merge${NC} → writing ${BASE}"
      if [[ "$DRY_RUN" == false ]]; then
        cp "$BASE" "$BAK_FILE"
        mv "$MERGED_FILE" "$BASE"
      fi
      (( CNT_MERGED++ )) || true

    elif [[ $DIFF3_EXIT -eq 1 ]]; then
      log "       ${RED}└─ CONFLICT${NC} → manual review required"
      if [[ "$DRY_RUN" == false ]]; then
        log "       ${RED}   Conflict markers written to: ${MERGED_FILE}${NC}"
        log "       ${YELLOW}   Original backed up to:       ${BAK_FILE}${NC}"
        cp "$BASE" "$BAK_FILE"
        # .merged file already written by diff3 above — leave it for manual review
      else
        # dry-run: clean up temp file
        [[ -f "$MERGED_FILE" ]] && rm -f "$MERGED_FILE"
      fi
      (( CNT_CONFLICT++ )) || true

    else
      log "       ${RED}└─ ERROR${NC} — diff3 returned exit code $DIFF3_EXIT"
      [[ -f "$MERGED_FILE" ]] && rm -f "$MERGED_FILE"
      (( CNT_SKIP++ )) || true
    fi

  fi

done < <(find "$SEARCH_ROOT" -type f -name "*.org" | sort)

# ── summary ───────────────────────────────────────────────────────────────────
log ""
log "${BOLD}=== Summary ===${NC}"
log "  ${GREEN}Clean apply  : ${CNT_CLEAN}${NC}"
log "  ${CYAN}Merged clean : ${CNT_MERGED}${NC}"
log "  ${RED}Conflicts    : ${CNT_CONFLICT}${NC}"
log "  ${YELLOW}Skipped      : ${CNT_SKIP}${NC}"

if [[ $CNT_CONFLICT -gt 0 ]]; then
  log ""
  log "${RED}${BOLD}ACTION REQUIRED:${NC} ${CNT_CONFLICT} conflict(s) need manual resolution."
  log "  1. Find conflict files:  find ${SEARCH_ROOT} -name '*.php.merged'"
  log "  2. Edit and resolve conflict markers (<<<<<<< / ======= / >>>>>>>)"
  log "  3. Replace original:     mv foo.php.merged foo.php"
  log "  4. Update baseline:      cp foo.php foo.php.org  (for next upgrade)"
fi

if [[ "$DRY_RUN" == true ]]; then
  log ""
  log "${YELLOW}Dry-run mode — no files were modified.${NC}"
fi

log ""
log "Done. Log saved to: ${CYAN}${LOG_FILE}${NC}"
