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

# ─────────────────────────────────────────────────────────────────────────────
# 03-pr-prepare.sh — Pull Request Pipeline for SecureGit
#
# Prepare a branch for pull request: run quality checks, security scan,
# generate PR description, validate commits, push, and create PR.
#
# Usage:
#   ./03-pr-prepare.sh              # Full interactive pipeline
#   ./03-pr-prepare.sh --check      # Run checks only (no PR creation)
#   ./03-pr-prepare.sh --push       # Push and create PR (skip checks)
#   ./03-pr-prepare.sh --dry-run    # Show what would happen
# ─────────────────────────────────────────────────────────────────────────────

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/securegit-common.sh"

sg_require_repo

# ─── Constants ───────────────────────────────────────────────────────────────
_REPO_ROOT="$(git rev-parse --show-toplevel)"
_REMOTE="${SG_PRIMARY_REMOTE:-origin}"
_DRY_RUN="${DRY_RUN:-false}"
_CHECK_ONLY="false"
_PUSH_ONLY="false"

# ─── Argument Parsing ────────────────────────────────────────────────────────
for arg in "$@"; do
    case "$arg" in
        --check)    _CHECK_ONLY="true" ;;
        --push)     _PUSH_ONLY="true" ;;
        --dry-run)  _DRY_RUN="true" ;;
        --help|-h)
            sg_info "Usage: $0 [--check|--push|--dry-run]"
            exit 0 ;;
        *)  sg_warn "Unknown argument: $arg" ;;
    esac
done

# ─── State ───────────────────────────────────────────────────────────────────
_CURRENT_BRANCH=""
_DEFAULT_BRANCH=""
_COMMIT_COUNT=0
_CHECK_RESULTS=()     # Array of "pass"/"fail"/"skip" for each check
_CHECK_NAMES=()       # Names of checks
_FAILURES=0

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

# Record a check result
_record_check() {
    local name="$1" result="$2"
    _CHECK_NAMES+=("$name")
    _CHECK_RESULTS+=("$result")
    [[ "$result" == "fail" ]] && (( _FAILURES++ )) || true
}

# Run a command as a check step
_run_check() {
    local name="$1"
    shift
    local cmd="$*"

    printf "  ${_C_CYAN}Running${_C_RESET}: %s\n" "$name"

    if [[ "$_DRY_RUN" == "true" ]]; then
        sg_info "  [dry-run] Would execute: $cmd"
        _record_check "$name" "skip"
        return 0
    fi

    if eval "$cmd" 2>&1 | while IFS= read -r line; do
        printf "    ${_C_DIM}%s${_C_RESET}\n" "$line"
    done; then
        sg_success "  $name passed."
        _record_check "$name" "pass"
        return 0
    else
        sg_error "  $name failed."
        _record_check "$name" "fail"
        return 1
    fi
}

# Count commits on current branch vs default
_count_branch_commits() {
    git rev-list --count "${_DEFAULT_BRANCH}..${_CURRENT_BRANCH}" 2>/dev/null || echo 0
}

# Get list of commits on this branch
_branch_commits() {
    git log --oneline "${_DEFAULT_BRANCH}..${_CURRENT_BRANCH}" 2>/dev/null
}

# Check if a commit message follows conventional commit format
_is_conventional() {
    local msg="$1"
    local types
    types="$(sg_conventional_types | tr ' ' '|')"
    echo "$msg" | grep -qP "^(${types})(\(.+\))?!?:" 2>/dev/null
}

# Detect if branch is part of a stack (based on parent branch)
_detect_stack() {
    local current="$1"
    local default="$2"

    # Check if the parent of this branch is not the default branch
    local merge_base
    merge_base="$(git merge-base "$default" "$current" 2>/dev/null || true)"

    if [[ -z "$merge_base" ]]; then
        echo "unknown"
        return
    fi

    # Look for branches that point between merge_base and current
    local intermediates=()
    while IFS= read -r branch; do
        [[ "$branch" == "$current" ]] && continue
        [[ "$branch" == "$default" ]] && continue

        local branch_tip
        branch_tip="$(git rev-parse "$branch" 2>/dev/null || true)"
        if [[ -n "$branch_tip" ]] && git merge-base --is-ancestor "$merge_base" "$branch_tip" 2>/dev/null; then
            if git merge-base --is-ancestor "$branch_tip" "$current" 2>/dev/null; then
                intermediates+=("$branch")
            fi
        fi
    done < <(git branch --format='%(refname:short)' 2>/dev/null)

    if (( ${#intermediates[@]} > 0 )); then
        echo "stacked on: ${intermediates[*]}"
    else
        echo "none"
    fi
}

# Generate PR description from commit log
_generate_pr_body() {
    local commits commit_count
    commits="$(_branch_commits)"
    commit_count="$(_count_branch_commits)"

    local body=""
    body+="## Summary\n\n"

    # Categorize commits by conventional type
    local has_feat=0 has_fix=0 has_refactor=0 has_docs=0 has_test=0 has_other=0
    local feat_items="" fix_items="" refactor_items="" docs_items="" test_items="" other_items=""

    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        local hash="${line%% *}"
        local msg="${line#* }"

        if echo "$msg" | grep -qP '^feat' 2>/dev/null; then
            feat_items+="- ${msg}\n"; has_feat=1
        elif echo "$msg" | grep -qP '^fix' 2>/dev/null; then
            fix_items+="- ${msg}\n"; has_fix=1
        elif echo "$msg" | grep -qP '^refactor' 2>/dev/null; then
            refactor_items+="- ${msg}\n"; has_refactor=1
        elif echo "$msg" | grep -qP '^docs' 2>/dev/null; then
            docs_items+="- ${msg}\n"; has_docs=1
        elif echo "$msg" | grep -qP '^test' 2>/dev/null; then
            test_items+="- ${msg}\n"; has_test=1
        else
            other_items+="- ${msg}\n"; has_other=1
        fi
    done <<< "$commits"

    (( has_feat ))     && body+="### Features\n${feat_items}\n"
    (( has_fix ))      && body+="### Bug Fixes\n${fix_items}\n"
    (( has_refactor )) && body+="### Refactoring\n${refactor_items}\n"
    (( has_docs ))     && body+="### Documentation\n${docs_items}\n"
    (( has_test ))     && body+="### Tests\n${test_items}\n"
    (( has_other ))    && body+="### Other Changes\n${other_items}\n"

    # Diff stats
    local stats
    stats="$(git diff --stat "${_DEFAULT_BRANCH}...${_CURRENT_BRANCH}" 2>/dev/null | tail -1 || echo "")"
    if [[ -n "$stats" ]]; then
        body+="## Diff Stats\n\n\`\`\`\n${stats}\n\`\`\`\n\n"
    fi

    # Ticket reference
    local ticket
    ticket="$(sg_ticket_from_branch)"
    if [[ -n "$ticket" ]]; then
        body+="## References\n\nTicket: ${ticket}\n\n"
    fi

    # Stack info
    local stack_info
    stack_info="$(_detect_stack "$_CURRENT_BRANCH" "$_DEFAULT_BRANCH")"
    if [[ "$stack_info" != "none" ]] && [[ "$stack_info" != "unknown" ]]; then
        body+="## Stack\n\nThis branch is ${stack_info}.\n\n"
    fi

    printf '%b' "$body"
}

# ─── Pipeline Steps ──────────────────────────────────────────────────────────

step_preflight() {
    sg_header "Pre-flight Checks"

    _CURRENT_BRANCH="$(sg_current_branch)"
    _DEFAULT_BRANCH="$(sg_default_branch)"
    _COMMIT_COUNT="$(_count_branch_commits)"

    sg_info "Branch:        $_CURRENT_BRANCH"
    sg_info "Default:       $_DEFAULT_BRANCH"
    sg_info "Commits:       $_COMMIT_COUNT"
    sg_info "Language:      $(sg_detect_language)"

    # Stack detection
    local stack_info
    stack_info="$(_detect_stack "$_CURRENT_BRANCH" "$_DEFAULT_BRANCH")"
    if [[ "$stack_info" != "none" ]] && [[ "$stack_info" != "unknown" ]]; then
        sg_warn "Stack detected: $_CURRENT_BRANCH is $stack_info"
    fi
    echo

    # Validate we're not on the default branch
    if [[ "$_CURRENT_BRANCH" == "$_DEFAULT_BRANCH" ]]; then
        sg_die "Cannot prepare PR from the default branch ($_DEFAULT_BRANCH). Create a feature branch first."
    fi

    # Validate there are commits to PR
    if (( _COMMIT_COUNT == 0 )); then
        sg_die "No commits on '$_CURRENT_BRANCH' vs '$_DEFAULT_BRANCH'. Nothing to PR."
    fi

    # Check for uncommitted changes
    local status
    status="$(git status --porcelain 2>/dev/null)"
    if [[ -n "$status" ]]; then
        sg_warn "Uncommitted changes detected:"
        git status --short
        echo
        if sg_confirm "Stash uncommitted changes before proceeding?"; then
            git stash push -m "auto-stash before PR prep" --include-untracked
            sg_success "Changes stashed."
        fi
    fi

    sg_success "Pre-flight checks passed."
}

step_check_uptodate() {
    sg_header "Branch Freshness"

    if ! sg_has_remote; then
        sg_warn "No remote configured. Skipping freshness check."
        _record_check "up-to-date" "skip"
        return 0
    fi

    sg_info "Fetching latest from ${_REMOTE}..."
    if [[ "$_DRY_RUN" == "true" ]]; then
        sg_info "[dry-run] Would fetch from ${_REMOTE}"
        _record_check "up-to-date" "skip"
        return 0
    fi

    git fetch "$_REMOTE" "$_DEFAULT_BRANCH" 2>/dev/null || true

    local behind
    behind="$(git rev-list --count "${_CURRENT_BRANCH}..${_REMOTE}/${_DEFAULT_BRANCH}" 2>/dev/null || echo 0)"

    if (( behind > 0 )); then
        sg_warn "Branch is $behind commit(s) behind ${_REMOTE}/${_DEFAULT_BRANCH}."

        if sg_confirm "Rebase onto ${_REMOTE}/${_DEFAULT_BRANCH}?"; then
            if git rebase "${_REMOTE}/${_DEFAULT_BRANCH}"; then
                sg_success "Rebased successfully."
                _record_check "up-to-date" "pass"
            else
                sg_error "Rebase failed. Resolve conflicts and retry."
                git rebase --abort 2>/dev/null || true
                _record_check "up-to-date" "fail"
                return 1
            fi
        else
            sg_warn "Proceeding with outdated branch."
            _record_check "up-to-date" "pass"
        fi
    else
        sg_success "Branch is up to date with ${_REMOTE}/${_DEFAULT_BRANCH}."
        _record_check "up-to-date" "pass"
    fi
}

step_validate_commits() {
    sg_header "Commit Validation"

    local commits total_commits=0 conventional_count=0 non_conventional=()

    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        (( total_commits++ )) || true

        local msg="${line#* }"
        if _is_conventional "$msg"; then
            (( conventional_count++ )) || true
        else
            non_conventional+=("$msg")
        fi
    done < <(_branch_commits)

    sg_info "Total commits: $total_commits"
    sg_info "Conventional:  $conventional_count / $total_commits"

    if (( ${#non_conventional[@]} > 0 )); then
        echo
        sg_warn "Non-conventional commits:"
        for msg in "${non_conventional[@]}"; do
            printf "  ${_C_YELLOW}- %s${_C_RESET}\n" "$msg"
        done
        echo
        sg_info "Expected format: type(scope): description"
        sg_info "Valid types: $(sg_conventional_types)"
        _record_check "conventional-commits" "fail"
    else
        sg_success "All commits follow conventional format."
        _record_check "conventional-commits" "pass"
    fi
}

step_run_formatter() {
    sg_header "Format Check"

    local fmt_cmd
    fmt_cmd="$(sg_detect_fmt_cmd)"

    if [[ "$fmt_cmd" == *"No formatter detected"* ]]; then
        sg_info "No formatter detected for $(sg_detect_language). Skipping."
        _record_check "format" "skip"
        return 0
    fi

    sg_info "Formatter: $fmt_cmd"
    _run_check "format" "$fmt_cmd" || true
}

step_run_linter() {
    sg_header "Lint Check"

    local lint_cmd
    lint_cmd="$(sg_detect_lint_cmd)"

    if [[ "$lint_cmd" == *"No linter detected"* ]]; then
        sg_info "No linter detected for $(sg_detect_language). Skipping."
        _record_check "lint" "skip"
        return 0
    fi

    sg_info "Linter: $lint_cmd"
    _run_check "lint" "$lint_cmd" || true
}

step_run_tests() {
    sg_header "Test Suite"

    local test_cmd
    test_cmd="$(sg_detect_test_cmd)"

    if [[ "$test_cmd" == *"No test command detected"* ]]; then
        sg_info "No test command detected for $(sg_detect_language). Skipping."
        _record_check "tests" "skip"
        return 0
    fi

    sg_info "Test command: $test_cmd"
    _run_check "tests" "$test_cmd" || true
}

step_security_scan() {
    sg_header "Security Scan"

    if [[ "$_DRY_RUN" == "true" ]]; then
        sg_info "[dry-run] Would run: securegit scan"
        _record_check "security-scan" "skip"
        return 0
    fi

    sg_info "Running securegit scan..."
    if _sg_cmd scan 2>&1 | while IFS= read -r line; do
        printf "    ${_C_DIM}%s${_C_RESET}\n" "$line"
    done; then
        sg_success "Security scan passed."
        _record_check "security-scan" "pass"
    else
        sg_error "Security scan found issues."
        _record_check "security-scan" "fail"
    fi
}

step_diff_stats() {
    sg_header "Diff Summary"

    local files_changed insertions deletions
    local stat_line
    stat_line="$(git diff --stat "${_DEFAULT_BRANCH}...${_CURRENT_BRANCH}" 2>/dev/null | tail -1 || echo "")"

    if [[ -z "$stat_line" ]]; then
        sg_warn "Could not compute diff stats."
        return 0
    fi

    sg_info "Changes vs ${_DEFAULT_BRANCH}:"
    echo "  $stat_line"
    echo

    # Warn if diff is very large
    local total_lines
    total_lines="$(git diff "${_DEFAULT_BRANCH}...${_CURRENT_BRANCH}" 2>/dev/null | wc -l | tr -d ' ')"
    local max_diff="${SG_MAX_DIFF_LINES:-200}"

    if (( total_lines > max_diff )); then
        sg_warn "Diff is $total_lines lines (threshold: $max_diff). Consider splitting into smaller PRs."
    fi

    # Show changed files
    sg_info "Changed files:"
    git diff --name-status "${_DEFAULT_BRANCH}...${_CURRENT_BRANCH}" 2>/dev/null | while IFS= read -r line; do
        local status="${line%%	*}"
        local file="${line#*	}"
        case "$status" in
            A) printf "  ${_C_GREEN}+ %s${_C_RESET}\n" "$file" ;;
            D) printf "  ${_C_RED}- %s${_C_RESET}\n" "$file" ;;
            M) printf "  ${_C_YELLOW}~ %s${_C_RESET}\n" "$file" ;;
            R*) printf "  ${_C_CYAN}> %s${_C_RESET}\n" "$file" ;;
            *) printf "  ${_C_DIM}  %s${_C_RESET}\n" "$file" ;;
        esac
    done
}

step_summary() {
    sg_header "Pipeline Summary"

    local i
    for i in "${!_CHECK_NAMES[@]}"; do
        local icon
        case "${_CHECK_RESULTS[$i]}" in
            pass) icon="${_C_GREEN}PASS${_C_RESET}" ;;
            fail) icon="${_C_RED}FAIL${_C_RESET}" ;;
            skip) icon="${_C_DIM}SKIP${_C_RESET}" ;;
        esac
        printf "  %-25s %b\n" "${_CHECK_NAMES[$i]}" "$icon"
    done

    echo
    if (( _FAILURES > 0 )); then
        sg_warn "$_FAILURES check(s) failed. PR may need attention."
    else
        sg_success "All checks passed."
    fi
}

step_push_and_pr() {
    sg_header "Push & PR Creation"

    if (( _FAILURES > 0 )); then
        sg_warn "$_FAILURES check(s) failed."
        if ! sg_confirm "Continue with PR creation despite failures?" "n"; then
            sg_info "Aborted. Fix issues and retry."
            return 0
        fi
    fi

    # Push branch
    if sg_has_remote; then
        sg_info "Pushing ${_CURRENT_BRANCH} to ${_REMOTE}..."
        if [[ "$_DRY_RUN" == "true" ]]; then
            sg_info "[dry-run] Would push to ${_REMOTE}/${_CURRENT_BRANCH}"
        else
            git push -u "$_REMOTE" "$_CURRENT_BRANCH" 2>&1 | while IFS= read -r line; do
                printf "    ${_C_DIM}%s${_C_RESET}\n" "$line"
            done
            sg_success "Pushed to ${_REMOTE}/${_CURRENT_BRANCH}."
        fi
    else
        sg_warn "No remote configured. Cannot push."
        return 1
    fi

    echo

    # Generate PR description
    sg_info "Generating PR description..."
    local pr_body
    pr_body="$(_generate_pr_body)"

    # Generate PR title from branch name
    local pr_title
    pr_title="$(echo "$_CURRENT_BRANCH" | sed -E 's|^[^/]+/||' | tr '-' ' ' | sed 's/^./\U&/')"

    # If first commit is conventional, use it as title
    local first_commit_msg
    first_commit_msg="$(git log --format='%s' "${_DEFAULT_BRANCH}..${_CURRENT_BRANCH}" 2>/dev/null | tail -1)"
    if [[ -n "$first_commit_msg" ]] && _is_conventional "$first_commit_msg"; then
        pr_title="$first_commit_msg"
    fi

    sg_info "PR Title: $pr_title"
    echo
    sg_info "PR Body:"
    sg_divider
    printf '%b' "$pr_body"
    sg_divider
    echo

    # Allow editing
    local final_title
    final_title="$(sg_prompt "PR title (press Enter to accept)" "$pr_title")"

    if [[ "$_DRY_RUN" == "true" ]]; then
        sg_info "[dry-run] Would create PR: $final_title"
        return 0
    fi

    # Create PR
    if command -v gh &>/dev/null; then
        sg_info "Creating PR via GitHub CLI..."
        local pr_url
        if pr_url="$(gh pr create \
            --title "$final_title" \
            --body "$(printf '%b' "$pr_body")" \
            --base "$_DEFAULT_BRANCH" \
            --head "$_CURRENT_BRANCH" 2>&1)"; then
            sg_success "PR created: $pr_url"
        else
            sg_error "Failed to create PR via gh CLI."
            sg_info "Error: $pr_url"
            echo
            _print_manual_instructions "$final_title" "$pr_body"
        fi
    else
        sg_warn "'gh' CLI not found."
        _print_manual_instructions "$final_title" "$pr_body"
    fi

    sg_undo_available
}

_print_manual_instructions() {
    local title="$1"
    local body="$2"

    sg_info "Create your PR manually:"
    echo
    sg_info "  Title: $title"
    sg_info "  Base:  $_DEFAULT_BRANCH"
    sg_info "  Head:  $_CURRENT_BRANCH"
    echo

    # Try to get the remote URL for a direct link
    local remote_url
    remote_url="$(git remote get-url "$_REMOTE" 2>/dev/null || true)"
    if [[ -n "$remote_url" ]]; then
        # Convert SSH to HTTPS URL
        local web_url
        web_url="$(echo "$remote_url" | sed -E 's|git@([^:]+):|https://\1/|' | sed 's|\.git$||')"
        sg_info "  URL: ${web_url}/compare/${_DEFAULT_BRANCH}...${_CURRENT_BRANCH}?expand=1"
    fi

    echo
    sg_info "PR body has been generated above. Copy it when creating your PR."
}

# ─── Main Pipeline ───────────────────────────────────────────────────────────

main() {
    sg_header "SecureGit PR Pipeline"

    local lang
    lang="$(sg_detect_language)"
    sg_info "Detected language: $lang"
    echo

    # Pre-flight always runs
    step_preflight

    if [[ "$_PUSH_ONLY" != "true" ]]; then
        # Quality checks
        step_check_uptodate
        step_validate_commits
        step_run_formatter
        step_run_linter
        step_run_tests
        step_security_scan
        step_diff_stats

        # Summary
        step_summary

        if [[ "$_CHECK_ONLY" == "true" ]]; then
            sg_info "Check-only mode. Skipping push and PR creation."
            exit $(( _FAILURES > 0 ? 1 : 0 ))
        fi
    fi

    echo
    if sg_confirm "Proceed to push and create PR?"; then
        step_push_and_pr
    else
        sg_info "Pipeline complete. Run again with --push to skip checks."
    fi
}

main
