#!/usr/bin/env bash
# git-status-report — Scan directories for git repos and report status
set -uo pipefail

# --- Colours ---
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
CYAN='\033[36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'

# --- Defaults ---
DRYRUN=false
TARGET_DIR="."

usage() {
    cat <<'USAGE'
Usage: git-status-report [OPTIONS] [DIRECTORY]

Recursively scan for git repositories and report uncommitted changes
and remote sync status.

Arguments:
  DIRECTORY          Root directory to scan (default: current directory)

Options:
  -n, --dryrun       List discovered repos without running status checks
  -h, --help         Show this help message
USAGE
}

# --- Parse args ---
while [[ $# -gt 0 ]]; do
    case "$1" in
        -n|--dryrun) DRYRUN=true; shift ;;
        -h|--help) usage; exit 0 ;;
        -*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
        *) TARGET_DIR="$1"; shift ;;
    esac
done

# Resolve to absolute path for display
TARGET_DIR="$(cd "$TARGET_DIR" 2>/dev/null && pwd)" || {
    echo "Error: cannot access directory '$TARGET_DIR'" >&2
    exit 2
}

# --- Discovery phase ---
discover_repos() {
    local dir="$1"
    local repos=()

    while IFS= read -r -d '' gitdir; do
        repos+=("$(dirname "$gitdir")")
    done < <(find -L "$dir" -name .git -type d -print0 2>/dev/null | sort -z)

    # Filter out nested repos (a repo whose path is a subdirectory of another)
    local filtered=()
    for repo in "${repos[@]}"; do
        local is_nested=false
        for other in "${filtered[@]}"; do
            if [[ "$repo" == "$other"/* ]]; then
                is_nested=true
                break
            fi
        done
        if ! $is_nested; then
            filtered+=("$repo")
        fi
    done

    printf '%s\n' "${filtered[@]}"
}

REPOS=()
while IFS= read -r repo; do
    [[ -n "$repo" ]] && REPOS+=("$repo")
done < <(discover_repos "$TARGET_DIR")

REPO_COUNT=${#REPOS[@]}

if [[ $REPO_COUNT -eq 0 ]]; then
    echo "No git repositories found in $TARGET_DIR"
    exit 0
fi

# --- Dryrun ---
if $DRYRUN; then
    echo "[dryrun] Would check $REPO_COUNT repositories:"
    for repo in "${REPOS[@]}"; do
        local_path="${repo#"$TARGET_DIR"}"
        [[ -z "$local_path" ]] && local_path="."
        local_path="${local_path#/}"
        echo "  $TARGET_DIR/$local_path"
    done
    exit 0
fi

# --- Status phase ---
DIRTY_REPOS=()
DIRTY_OUTPUT=()
CLEAN_COUNT=0

get_char_diff() {
    local repo="$1"
    local file="$2"
    local status="$3"

    if [[ "$status" == "?" ]]; then
        # Untracked: count all characters as added
        local target="$repo/$file"
        if [[ -d "$target" ]]; then
            # Directory: sum all file sizes recursively
            local chars
            chars=$(find "$target" -type f -exec cat {} + 2>/dev/null | wc -c || echo 0)
            echo "+${chars}"
            return
        fi
        if file "$target" 2>/dev/null | grep -qP "binary|image|archive" && ! file "$target" 2>/dev/null | grep -q "text"; then
            echo "(binary)"
            return
        fi
        local chars
        chars=$(wc -c < "$target" 2>/dev/null || echo 0)
        echo "+${chars}"
        return
    fi

    if [[ "$status" == "D" ]]; then
        # Deleted: count from last committed version
        local chars
        chars=$(git -C "$repo" show "HEAD:$file" 2>/dev/null | wc -c || echo 0)
        if [[ "$chars" == "0" ]]; then
            echo "(binary)"
        else
            echo "-${chars}"
        fi
        return
    fi

    # Modified: diff character counts
    local diff_output
    diff_output=$(git -C "$repo" diff HEAD -- "$file" 2>/dev/null || git -C "$repo" diff -- "$file" 2>/dev/null || true)

    if [[ -z "$diff_output" ]]; then
        # Staged but no diff against HEAD — try cached
        diff_output=$(git -C "$repo" diff --cached -- "$file" 2>/dev/null || true)
    fi

    # Check for binary
    if echo "$diff_output" | grep -q "Binary files"; then
        echo "(binary)"
        return
    fi

    local added=0 removed=0
    while IFS= read -r line; do
        case "$line" in
            +*) added=$((added + ${#line} - 1)) ;;  # -1 for the leading +
            -*) removed=$((removed + ${#line} - 1)) ;;
        esac
    done < <(echo "$diff_output" | grep -E '^\+[^+]|^-[^-]' || true)

    echo "+${added} / -${removed}"
}

get_remote_status() {
    local repo="$1"
    local output=""

    local branch
    branch=$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null || echo "")

    if [[ -z "$branch" ]]; then
        # Return empty — detached HEAD alone isn't a divergence.
        return
    fi

    local remotes
    remotes=$(git -C "$repo" remote 2>/dev/null || true)

    if [[ -z "$remotes" ]]; then
        # Return empty — "no remotes" is not a divergence.
        # The caller adds this note only when the repo is already dirty.
        return
    fi

    while IFS= read -r remote; do
        [[ -z "$remote" ]] && continue

        # Fetch is too slow for a status report — use local tracking info
        local remote_branch="${remote}/${branch}"

        if ! git -C "$repo" rev-parse --verify "$remote_branch" &>/dev/null; then
            output+="  ${DIM}↕ ${remote}/${branch}: (no upstream)${RESET}\n"
            continue
        fi

        local counts
        counts=$(git -C "$repo" rev-list --left-right --count "${branch}...${remote_branch}" 2>/dev/null || echo "0	0")
        local ahead behind
        ahead=$(echo "$counts" | cut -f1)
        behind=$(echo "$counts" | cut -f2)

        if [[ "$ahead" -eq 0 && "$behind" -eq 0 ]]; then
            continue  # In sync — nothing to report
        fi

        local colour="$YELLOW"
        output+="  ${colour}↕ ${remote}/${branch}: ${ahead} ahead, ${behind} behind${RESET}\n"
    done <<< "$remotes"

    [[ -n "$output" ]] && echo -e "$output"
}

for repo in "${REPOS[@]}"; do
    rel_path="${repo#"$TARGET_DIR"}"
    [[ -z "$rel_path" ]] && rel_path="."
    rel_path="${rel_path#/}"

    # Get porcelain status
    porcelain=$(git -C "$repo" status --porcelain 2>/dev/null || true)
    remote_info=$(get_remote_status "$repo")

    if [[ -z "$porcelain" && -z "$remote_info" ]]; then
        CLEAN_COUNT=$((CLEAN_COUNT + 1))
        continue
    fi

    # Build output for this repo
    section=""
    section+="$(printf "${BOLD}── dirty: %s ──${RESET}" "$rel_path")\n"

    if [[ -n "$porcelain" ]]; then
        while IFS= read -r line; do
            [[ -z "$line" ]] && continue
            local_status="${line:0:2}"
            file="${line:3}"

            # Determine primary status character
            status_char="${local_status:0:1}"
            [[ "$status_char" == " " ]] && status_char="${local_status:1:1}"
            [[ "$status_char" == "?" ]] && status_char="?"

            # Format the status display
            case "$status_char" in
                M) display_status="${YELLOW} M${RESET}" ;;
                A) display_status="${GREEN} A${RESET}" ;;
                D) display_status="${RED} D${RESET}" ;;
                R) display_status="${CYAN} R${RESET}" ;;
                ?) display_status="${RED}??${RESET}" ;;
                *) display_status=" ${status_char}" ;;
            esac

            char_diff=$(get_char_diff "$repo" "$file" "$status_char")
            section+="$(printf "  %b %-30s %s" "$display_status" "$file" "$char_diff")\n"
        done <<< "$porcelain"
    fi

    if [[ -n "$remote_info" ]]; then
        section+="$remote_info"
    elif [[ -n "$porcelain" ]]; then
        # Show "(no remotes)" only when there are local changes
        has_remotes=$(git -C "$repo" remote 2>/dev/null || true)
        if [[ -z "$has_remotes" ]]; then
            section+="  ${DIM}(no remotes)${RESET}\n"
        fi
    fi

    DIRTY_REPOS+=("$rel_path")
    DIRTY_OUTPUT+=("$section")
done

# --- Report phase ---
echo ""
echo "Scanned $REPO_COUNT repositories in $TARGET_DIR"
echo ""

for output in "${DIRTY_OUTPUT[@]}"; do
    echo -e "$output"
done

if [[ $CLEAN_COUNT -gt 0 ]]; then
    printf "${GREEN}✓ %d repositories are clean and in sync.${RESET}\n" "$CLEAN_COUNT"
elif [[ ${#DIRTY_REPOS[@]} -eq 0 ]]; then
    printf "${GREEN}✓ All %d repositories are clean and in sync.${RESET}\n" "$REPO_COUNT"
fi

# Exit 1 if any repo is dirty or out of sync
if [[ ${#DIRTY_REPOS[@]} -gt 0 ]]; then
    exit 1
fi
exit 0
