#!/usr/bin/env bash
# mirror_refs.sh — Additive ref mirroring between two Gitea instances.
#
# Mirrors branches and tags between dadeschools and prgs Timesheet repos.
# Safe by default: dry-run, additive-only, divergence-skipping.
#
# Usage:
#   ./mirror_refs.sh                  # dry-run (default) — shows what would happen
#   ./mirror_refs.sh --apply          # actually push
#   ./mirror_refs.sh --apply --force  # also force-push diverged branches
#
# Auth:
#   dadeschools: HTTPS via `git credential fill` (SSH:2222 flaky)
#   prgs:        SSH via ssh://git@gitea-ssh.prgs.cc:2222 (SSH ok)
#
# Safety:
#   - Additive only: never deletes branches/tags.
#   - Divergence protection: if a shared branch has diverged (no FF path),
#     skip + warn unless --force is passed.
#   - Won't auto-close/merge anything — just ref mirroring.
set -euo pipefail

# ── Configuration ─────────────────────────────────────────────────────────────

DS_HOST="gitea.dadeschools.net"
DS_ORG="Contractor"
DS_REPO="Timesheet"

PRGS_SSH_URL="ssh://git@gitea-ssh.prgs.cc:2222/Scaled-Tech-Consulting/Timesheet.git"

CACHE_DIR="/tmp/gitea-mirror-${DS_REPO}"

# ── Flag parsing ──────────────────────────────────────────────────────────────

DRY_RUN=true
FORCE=false

for arg in "$@"; do
    case "$arg" in
        --apply)  DRY_RUN=false ;;
        --force)  FORCE=true ;;
        --help|-h)
            sed -n '2,/^$/{ s/^# //; s/^#$//; p; }' "$0"
            exit 0
            ;;
        *)
            echo "Unknown flag: $arg (try --help)" >&2
            exit 1
            ;;
    esac
done

if $DRY_RUN; then
    echo "═══ DRY RUN (pass --apply to execute) ═══"
    echo ""
fi

# ── Helpers ───────────────────────────────────────────────────────────────────

log()  { printf "  %s\n" "$*"; }
ok()   { printf "  ✓ %s\n" "$*"; }
warn() { printf "  ⚠ %s\n" "$*" >&2; }
err()  { printf "  ✗ %s\n" "$*" >&2; }

# Build an authenticated HTTPS URL for dadeschools using the macOS keychain.
get_ds_url() {
    local creds user pass
    creds=$(printf "host=%s\nprotocol=https\n\n" "$DS_HOST" | git credential fill 2>/dev/null) || true
    user=$(printf '%s\n' "$creds" | sed -n 's/^username=//p')
    pass=$(printf '%s\n' "$creds" | sed -n 's/^password=//p')
    if [[ -z "$user" || -z "$pass" ]]; then
        err "Could not get credentials for $DS_HOST"
        err "Ensure you've logged in via HTTPS at least once (git credential fill)."
        exit 1
    fi
    echo "https://${user}:${pass}@${DS_HOST}/${DS_ORG}/${DS_REPO}.git"
}

# Run a git command, or print it if dry-run.
run_or_dry() {
    if $DRY_RUN; then
        log "[dry-run] $*"
    else
        "$@"
    fi
}

# ── Set up bare mirror cache ─────────────────────────────────────────────────

setup_cache() {
    if [[ ! -d "$CACHE_DIR" ]]; then
        log "Creating mirror cache at $CACHE_DIR"
        git init --bare "$CACHE_DIR" --quiet
    fi

    local ds_url
    ds_url=$(get_ds_url)

    # Set/update remotes
    git -C "$CACHE_DIR" remote remove ds   2>/dev/null || true
    git -C "$CACHE_DIR" remote remove prgs 2>/dev/null || true
    git -C "$CACHE_DIR" remote add ds   "$ds_url"
    git -C "$CACHE_DIR" remote add prgs "$PRGS_SSH_URL"
}

# ── Fetch ─────────────────────────────────────────────────────────────────────

fetch_both() {
    echo "── Fetching dadeschools ──"
    git -C "$CACHE_DIR" fetch ds   --tags --prune --quiet 2>&1 | sed 's/^/  /'
    ok "dadeschools fetched"

    echo "── Fetching prgs ──"
    git -C "$CACHE_DIR" fetch prgs --tags --prune --quiet 2>&1 | sed 's/^/  /'
    ok "prgs fetched"
    echo ""
}

# ── Branch mirroring ──────────────────────────────────────────────────────────

mirror_branches() {
    echo "── Branch comparison ──"

    # Collect branch names from each remote (strip "refs/remotes/<remote>/")
    local -A ds_branches prgs_branches
    local ref
    while IFS= read -r ref; do
        local name="${ref#refs/remotes/ds/}"
        [[ "$name" == "HEAD" ]] && continue
        ds_branches["$name"]=$(git -C "$CACHE_DIR" rev-parse "$ref")
    done < <(git -C "$CACHE_DIR" for-each-ref --format='%(refname)' refs/remotes/ds/)

    while IFS= read -r ref; do
        local name="${ref#refs/remotes/prgs/}"
        [[ "$name" == "HEAD" ]] && continue
        prgs_branches["$name"]=$(git -C "$CACHE_DIR" rev-parse "$ref")
    done < <(git -C "$CACHE_DIR" for-each-ref --format='%(refname)' refs/remotes/prgs/)

    local total_ds=${#ds_branches[@]}
    local total_prgs=${#prgs_branches[@]}
    log "dadeschools: $total_ds branches, prgs: $total_prgs branches"

    local pushed=0 skipped=0

    # All unique branch names
    local -A all_branches
    for b in "${!ds_branches[@]}"; do all_branches["$b"]=1; done
    for b in "${!prgs_branches[@]}"; do all_branches["$b"]=1; done

    for b in $(printf '%s\n' "${!all_branches[@]}" | sort); do
        local ds_sha="${ds_branches[$b]:-}"
        local prgs_sha="${prgs_branches[$b]:-}"

        if [[ -n "$ds_sha" && -z "$prgs_sha" ]]; then
            # Only on dadeschools → push to prgs
            ok "$b → prgs (new)"
            run_or_dry git -C "$CACHE_DIR" push prgs "refs/remotes/ds/${b}:refs/heads/${b}"
            ((pushed++)) || true

        elif [[ -z "$ds_sha" && -n "$prgs_sha" ]]; then
            # Only on prgs → push to dadeschools
            ok "$b → dadeschools (new)"
            run_or_dry git -C "$CACHE_DIR" push ds "refs/remotes/prgs/${b}:refs/heads/${b}"
            ((pushed++)) || true

        elif [[ "$ds_sha" == "$prgs_sha" ]]; then
            # In sync — nothing to do
            log "$b ✔ in sync (${ds_sha:0:8})"

        else
            # Both have it, different SHAs — check fast-forward
            if git -C "$CACHE_DIR" merge-base --is-ancestor "$ds_sha" "$prgs_sha" 2>/dev/null; then
                # dadeschools is behind prgs — push prgs → ds
                ok "$b → dadeschools (ff: ${ds_sha:0:8}..${prgs_sha:0:8})"
                run_or_dry git -C "$CACHE_DIR" push ds "refs/remotes/prgs/${b}:refs/heads/${b}"
                ((pushed++)) || true

            elif git -C "$CACHE_DIR" merge-base --is-ancestor "$prgs_sha" "$ds_sha" 2>/dev/null; then
                # prgs is behind dadeschools — push ds → prgs
                ok "$b → prgs (ff: ${prgs_sha:0:8}..${ds_sha:0:8})"
                run_or_dry git -C "$CACHE_DIR" push prgs "refs/remotes/ds/${b}:refs/heads/${b}"
                ((pushed++)) || true

            else
                # Diverged
                if $FORCE; then
                    warn "$b DIVERGED — force-pushing dadeschools → prgs"
                    run_or_dry git -C "$CACHE_DIR" push prgs --force "refs/remotes/ds/${b}:refs/heads/${b}"
                    ((pushed++)) || true
                else
                    warn "$b DIVERGED (ds: ${ds_sha:0:8}, prgs: ${prgs_sha:0:8}) — skipping (use --force)"
                    ((skipped++)) || true
                fi
            fi
        fi
    done

    echo ""
    log "Branches: $pushed pushed, $skipped skipped"
    echo ""
}

# ── Tag mirroring ─────────────────────────────────────────────────────────────

mirror_tags() {
    echo "── Tag comparison ──"

    local -A ds_tags prgs_tags
    local ref

    while IFS= read -r ref; do
        [[ -z "$ref" ]] && continue
        local name="${ref#refs/tags/}"
        # Dereference annotated tags to the underlying commit
        ds_tags["$name"]=$(git -C "$CACHE_DIR" rev-parse "${ref}^{}" 2>/dev/null || git -C "$CACHE_DIR" rev-parse "$ref")
    done < <(git -C "$CACHE_DIR" for-each-ref --format='%(refname)' "refs/tags/" 2>/dev/null)

    # Tags are global in git (not per-remote), so after fetching both with --tags,
    # they're merged into refs/tags/. We need to check which remote actually has
    # each tag by checking if the tagged commit is reachable from that remote's refs.
    # Simpler approach: fetch tags explicitly per remote and compare via ls-remote.

    local -A ds_remote_tags prgs_remote_tags

    while IFS=$'\t' read -r sha ref; do
        [[ -z "$ref" ]] && continue
        local name="${ref#refs/tags/}"
        name="${name%\^\{\}}"  # strip ^{} suffix from dereferenced tags
        ds_remote_tags["$name"]="$sha"
    done < <(git -C "$CACHE_DIR" ls-remote --tags ds 2>/dev/null)

    while IFS=$'\t' read -r sha ref; do
        [[ -z "$ref" ]] && continue
        local name="${ref#refs/tags/}"
        name="${name%\^\{\}}"
        prgs_remote_tags["$name"]="$sha"
    done < <(git -C "$CACHE_DIR" ls-remote --tags prgs 2>/dev/null)

    local total_ds=${#ds_remote_tags[@]}
    local total_prgs=${#prgs_remote_tags[@]}
    log "dadeschools: $total_ds tags, prgs: $total_prgs tags"

    local pushed=0 skipped=0

    # Combine all tag names
    local -A all_tags
    for t in "${!ds_remote_tags[@]}"; do all_tags["$t"]=1; done
    for t in "${!prgs_remote_tags[@]}"; do all_tags["$t"]=1; done

    for t in $(printf '%s\n' "${!all_tags[@]}" | sort -V); do
        local ds_sha="${ds_remote_tags[$t]:-}"
        local prgs_sha="${prgs_remote_tags[$t]:-}"

        if [[ -n "$ds_sha" && -z "$prgs_sha" ]]; then
            ok "$t → prgs (new tag)"
            run_or_dry git -C "$CACHE_DIR" push prgs "refs/tags/${t}:refs/tags/${t}"
            ((pushed++)) || true

        elif [[ -z "$ds_sha" && -n "$prgs_sha" ]]; then
            ok "$t → dadeschools (new tag)"
            run_or_dry git -C "$CACHE_DIR" push ds "refs/tags/${t}:refs/tags/${t}"
            ((pushed++)) || true

        elif [[ "$ds_sha" == "$prgs_sha" ]]; then
            log "$t ✔ in sync"

        else
            warn "$t MISMATCH (ds: ${ds_sha:0:8}, prgs: ${prgs_sha:0:8}) — tags shouldn't diverge, skipping"
            ((skipped++)) || true
        fi
    done

    echo ""
    log "Tags: $pushed pushed, $skipped skipped"
    echo ""
}

# ── Main ──────────────────────────────────────────────────────────────────────

main() {
    echo ""
    echo "╔══════════════════════════════════════════════╗"
    echo "║  Gitea Ref Mirror: dadeschools ⇄ prgs       ║"
    echo "╚══════════════════════════════════════════════╝"
    echo ""

    setup_cache
    fetch_both
    mirror_branches
    mirror_tags

    if $DRY_RUN; then
        echo "═══ DRY RUN complete. Pass --apply to execute. ═══"
    else
        echo "═══ Mirror complete. ═══"
    fi
}

main
