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

# ─────────────────────────────────────────────────────────────────────────────
# 18-quality-check.sh — Local Quality Checks for SecureGit
#
# Run quality checks (lint, format, test, build, security scan) on current
# changes with auto-detection, changed-file targeting, timing dashboard,
# and quick/full modes.
#
# Usage:
#   ./18-quality-check.sh              # Full quality check (interactive)
#   ./18-quality-check.sh quick        # Quick mode: lint + format only
#   ./18-quality-check.sh full         # Full mode: all checks, no prompts
#   ./18-quality-check.sh lint         # Run only lint check
#   ./18-quality-check.sh format       # Run only format check
#   ./18-quality-check.sh test         # Run only test check
#   ./18-quality-check.sh build        # Run only build check
#   ./18-quality-check.sh scan         # Run only security scan
#   ./18-quality-check.sh compare      # Compare quality against default branch
# ─────────────────────────────────────────────────────────────────────────────

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

sg_require_repo

# ─── Constants ───────────────────────────────────────────────────────────────
_REPO_ROOT="$(git rev-parse --show-toplevel)"
_RESULTS_DIR="$_REPO_ROOT/.securegit/quality"
_OVERALL_EXIT=0

# ─── Check Result Tracking ──────────────────────────────────────────────────
declare -A _CHECK_STATUS    # pass, fail, skip
declare -A _CHECK_DURATION  # seconds
declare -A _CHECK_OUTPUT    # captured output (last N lines)

_start_time() {
    date +%s%N
}

_elapsed_secs() {
    local start="$1"
    local end
    end="$(date +%s%N)"
    local diff=$(( (end - start) / 1000000 ))
    printf "%d.%03d" $(( diff / 1000 )) $(( diff % 1000 ))
}

_record_result() {
    local name="$1"
    local status="$2"  # pass, fail, skip
    local duration="$3"
    local output="${4:-}"

    _CHECK_STATUS[$name]="$status"
    _CHECK_DURATION[$name]="$duration"
    _CHECK_OUTPUT[$name]="$output"

    if [[ "$status" == "fail" ]]; then
        _OVERALL_EXIT=1
    fi
}

# ─── Changed File Detection ─────────────────────────────────────────────────

# Get files changed relative to default branch
_changed_files() {
    local base
    base="$(sg_default_branch)"

    # Files changed in working tree + staged
    local changed=""

    # Staged files
    changed="$(git diff --cached --name-only 2>/dev/null || true)"

    # Unstaged tracked files
    local unstaged
    unstaged="$(git diff --name-only 2>/dev/null || true)"
    if [[ -n "$unstaged" ]]; then
        changed="$(printf "%s\n%s" "$changed" "$unstaged")"
    fi

    # If nothing staged/modified, compare against default branch
    if [[ -z "$(echo "$changed" | xargs)" ]]; then
        if git show-ref --verify --quiet "refs/heads/$base" 2>/dev/null; then
            changed="$(git diff --name-only "$base"...HEAD 2>/dev/null || true)"
        fi
    fi

    # Deduplicate and filter existing files
    echo "$changed" | sort -u | while IFS= read -r f; do
        [[ -n "$f" ]] && [[ -f "$_REPO_ROOT/$f" ]] && echo "$f"
    done
}

# Filter changed files by extension
_changed_files_by_ext() {
    local exts="$1"  # pipe-separated: "js|ts|tsx"
    _changed_files | grep -E "\.(${exts})$" || true
}

# Get the list of relevant changed files for the detected language
_changed_files_for_lang() {
    local lang
    lang="$(sg_detect_language)"
    case "$lang" in
        rust)    _changed_files_by_ext "rs" ;;
        go)      _changed_files_by_ext "go" ;;
        python)  _changed_files_by_ext "py" ;;
        js)      _changed_files_by_ext "js|ts|tsx|jsx|mjs|cjs" ;;
        java)    _changed_files_by_ext "java|kt|kts" ;;
        ruby)    _changed_files_by_ext "rb" ;;
        php)     _changed_files_by_ext "php" ;;
        c)       _changed_files_by_ext "c|h|cpp|hpp|cc" ;;
        *)       _changed_files ;;
    esac
}

# ─── Individual Check Runners ────────────────────────────────────────────────

_run_check() {
    local name="$1"
    local cmd="$2"
    local changed_only="${3:-false}"
    local files="${4:-}"

    # Skip if command is an echo (no tool detected)
    if [[ "$cmd" == echo\ * ]]; then
        _record_result "$name" "skip" "0.000" "No $name tool detected"
        return 0
    fi

    sg_info "Running $name..."

    local start
    start="$(_start_time)"
    local output=""
    local exit_code=0

    # If changed_only and we have files, try to pass them to the command
    local effective_cmd="$cmd"
    if [[ "$changed_only" == "true" ]] && [[ -n "$files" ]]; then
        # Some tools accept file arguments; append them
        case "$name" in
            lint)
                case "$cmd" in
                    *eslint*|*biome\ check*|*ruff\ check*|*flake8*|*rubocop*|*phpstan*)
                        effective_cmd="$cmd $files"
                        ;;
                    *clippy*|*golangci-lint*)
                        # These don't take individual files well; run full
                        effective_cmd="$cmd"
                        ;;
                esac
                ;;
            format)
                case "$cmd" in
                    *prettier*|*biome\ format*|*ruff\ format*|*black*)
                        effective_cmd="$cmd $files"
                        ;;
                    *cargo\ fmt*|*gofmt*)
                        effective_cmd="$cmd"
                        ;;
                esac
                ;;
            *)
                effective_cmd="$cmd"
                ;;
        esac
    fi

    # Run the command and capture output
    output="$(cd "$_REPO_ROOT" && eval "$effective_cmd" 2>&1)" || exit_code=$?

    local duration
    duration="$(_elapsed_secs "$start")"

    # Truncate output for storage (keep last 50 lines)
    local short_output
    short_output="$(echo "$output" | tail -50)"

    if (( exit_code == 0 )); then
        _record_result "$name" "pass" "$duration" "$short_output"
        sg_success "$name passed (${duration}s)"
    else
        _record_result "$name" "fail" "$duration" "$short_output"
        sg_error "$name failed (${duration}s)"
        # Show failure output
        if [[ -n "$short_output" ]]; then
            echo "$short_output" | head -30
            local total_lines
            total_lines="$(echo "$output" | wc -l)"
            if (( total_lines > 30 )); then
                sg_info "... ($((total_lines - 30)) more lines, full output in check log)"
            fi
        fi
    fi

    return 0  # Don't fail the whole script on individual check failure
}

run_lint() {
    local cmd
    cmd="$(sg_detect_lint_cmd)"
    local files
    files="$(_changed_files_for_lang | tr '\n' ' ')"
    local changed_only="false"
    if [[ -n "$files" ]]; then
        changed_only="true"
    fi
    _run_check "lint" "$cmd" "$changed_only" "$files"
}

run_format() {
    local cmd
    cmd="$(sg_detect_fmt_cmd)"
    local files
    files="$(_changed_files_for_lang | tr '\n' ' ')"
    local changed_only="false"
    if [[ -n "$files" ]]; then
        changed_only="true"
    fi
    _run_check "format" "$cmd" "$changed_only" "$files"
}

run_test() {
    local cmd
    cmd="$(sg_detect_test_cmd)"
    _run_check "test" "$cmd" "false" ""
}

run_build() {
    local cmd
    cmd="$(sg_detect_build_cmd)"
    _run_check "build" "$cmd" "false" ""
}

run_scan() {
    sg_info "Running security scan..."
    local start
    start="$(_start_time)"
    local output=""
    local exit_code=0

    output="$(_sg_cmd scan "$_REPO_ROOT" 2>&1)" || exit_code=$?

    local duration
    duration="$(_elapsed_secs "$start")"

    local short_output
    short_output="$(echo "$output" | tail -50)"

    if (( exit_code == 0 )); then
        _record_result "security" "pass" "$duration" "$short_output"
        sg_success "Security scan passed (${duration}s)"
    else
        _record_result "security" "fail" "$duration" "$short_output"
        sg_error "Security scan found issues (${duration}s)"
        if [[ -n "$short_output" ]]; then
            echo "$short_output" | head -20
        fi
    fi
}

# ─── Dashboard ───────────────────────────────────────────────────────────────

_show_dashboard() {
    sg_header "Quality Check Results"

    local lang
    lang="$(sg_detect_language)"
    local branch
    branch="$(sg_current_branch)"
    local changed_count
    changed_count="$(_changed_files | wc -l | tr -d ' ')"

    sg_info "Language: $lang | Branch: $branch | Changed files: $changed_count"
    echo

    printf "  ${_C_BOLD}%-14s %-10s %s${_C_RESET}\n" "CHECK" "STATUS" "DURATION"
    sg_divider

    local checks=("lint" "format" "test" "build" "security")
    local pass_count=0
    local fail_count=0
    local skip_count=0

    for check in "${checks[@]}"; do
        local status="${_CHECK_STATUS[$check]:-skip}"
        local duration="${_CHECK_DURATION[$check]:-0.000}"
        local status_display

        case "$status" in
            pass)
                status_display="${_C_GREEN}PASS${_C_RESET}"
                (( pass_count++ )) || true
                ;;
            fail)
                status_display="${_C_RED}FAIL${_C_RESET}"
                (( fail_count++ )) || true
                ;;
            skip)
                status_display="${_C_DIM}SKIP${_C_RESET}"
                (( skip_count++ )) || true
                ;;
        esac

        printf "  %-14s " "$check"
        printf "$status_display"
        printf "      ${_C_DIM}%ss${_C_RESET}\n" "$duration"
    done

    sg_divider
    printf "  ${_C_BOLD}Results:${_C_RESET} "
    printf "${_C_GREEN}%d passed${_C_RESET}  " "$pass_count"
    printf "${_C_RED}%d failed${_C_RESET}  " "$fail_count"
    printf "${_C_DIM}%d skipped${_C_RESET}\n" "$skip_count"
    echo

    if (( fail_count > 0 )); then
        printf "  ${_C_BOLD}${_C_RED}Overall: FAIL${_C_RESET}\n"
    else
        printf "  ${_C_BOLD}${_C_GREEN}Overall: PASS${_C_RESET}\n"
    fi

    # Save results to file
    mkdir -p "$_RESULTS_DIR"
    local results_file="$_RESULTS_DIR/latest.txt"
    {
        echo "# Quality Check Results - $(date -Iseconds)"
        echo "# Branch: $branch | Language: $lang | Changed files: $changed_count"
        for check in "${checks[@]}"; do
            printf "%s|%s|%s\n" "$check" "${_CHECK_STATUS[$check]:-skip}" "${_CHECK_DURATION[$check]:-0.000}"
        done
    } > "$results_file"
}

_show_watch_hint() {
    echo
    sg_info "Watch mode suggestions for continuous checking:"
    local lang
    lang="$(sg_detect_language)"

    case "$lang" in
        rust)
            sg_info "  cargo watch -x check -x clippy -x test"
            sg_info "  Install: cargo install cargo-watch"
            ;;
        go)
            sg_info "  air (live reload): github.com/cosmtrek/air"
            sg_info "  Install: go install github.com/cosmtrek/air@latest"
            ;;
        python)
            sg_info "  pytest-watch: ptw -- --last-failed"
            sg_info "  Install: pip install pytest-watch"
            ;;
        js)
            sg_info "  vitest --watch    (if using vitest)"
            sg_info "  jest --watch      (if using jest)"
            sg_info "  npx eslint --watch (with eslint-watch)"
            ;;
        java)
            sg_info "  ./gradlew --continuous test"
            sg_info "  ./mvnw fizzed-watcher:run"
            ;;
        ruby)
            sg_info "  guard (with Guardfile): bundle exec guard"
            sg_info "  Install: gem install guard guard-rspec"
            ;;
        php)
            sg_info "  phpunit-watcher watch"
            sg_info "  Install: composer require spatie/phpunit-watcher --dev"
            ;;
        *)
            sg_info "  entr: find . -name '*.ext' | entr -c make test"
            sg_info "  Install: apt/brew install entr"
            ;;
    esac
}

# ─── Operations ──────────────────────────────────────────────────────────────

op_quick() {
    sg_header "Quick Quality Check (lint + format)"

    local lang
    lang="$(sg_detect_language)"
    local changed
    changed="$(_changed_files | wc -l | tr -d ' ')"
    sg_info "Language: $lang | Changed files: $changed"
    echo

    run_lint
    echo
    run_format

    echo
    _show_dashboard
}

op_full() {
    sg_header "Full Quality Check"

    local lang
    lang="$(sg_detect_language)"
    local changed
    changed="$(_changed_files | wc -l | tr -d ' ')"
    sg_info "Language: $lang | Changed files: $changed"
    echo

    run_lint
    echo
    run_format
    echo
    run_test
    echo
    run_build
    echo
    run_scan

    echo
    _show_dashboard
    _show_watch_hint
}

op_compare() {
    sg_header "Quality Comparison Against Default Branch"

    local base
    base="$(sg_default_branch)"
    local current
    current="$(sg_current_branch)"

    if [[ "$current" == "$base" ]]; then
        sg_warn "Already on the default branch ($base). Nothing to compare."
        return 0
    fi

    sg_info "Comparing quality: $current vs $base"
    echo

    # Run checks on current branch
    sg_info "Running checks on current branch ($current)..."
    run_lint
    run_format
    run_test
    run_build
    run_scan

    local current_results=()
    local checks=("lint" "format" "test" "build" "security")
    for check in "${checks[@]}"; do
        current_results+=("${_CHECK_STATUS[$check]:-skip}")
    done

    # Save current results
    local current_exit=$_OVERALL_EXIT

    # Check if we have baseline results
    local baseline_file="$_RESULTS_DIR/baseline-${base}.txt"

    if [[ -f "$baseline_file" ]]; then
        sg_info "Comparing against saved baseline for $base..."
        echo

        printf "  ${_C_BOLD}%-14s %-14s %-14s %s${_C_RESET}\n" "CHECK" "CURRENT" "BASELINE" "TREND"
        sg_divider

        local i=0
        for check in "${checks[@]}"; do
            local curr="${current_results[$i]}"
            local base_status
            base_status="$(grep "^${check}|" "$baseline_file" 2>/dev/null | cut -d'|' -f2 || echo "skip")"

            local trend=""
            if [[ "$curr" == "pass" ]] && [[ "$base_status" == "fail" ]]; then
                trend="${_C_GREEN}improved${_C_RESET}"
            elif [[ "$curr" == "fail" ]] && [[ "$base_status" == "pass" ]]; then
                trend="${_C_RED}regressed${_C_RESET}"
            elif [[ "$curr" == "$base_status" ]]; then
                trend="${_C_DIM}unchanged${_C_RESET}"
            else
                trend="${_C_DIM}-${_C_RESET}"
            fi

            printf "  %-14s %-14s %-14s " "$check" "$curr" "$base_status"
            printf "$trend\n"
            (( i++ )) || true
        done
    else
        sg_warn "No baseline results found for $base."
        sg_info "Run this check on $base to create a baseline:"
        sg_info "  git checkout $base && ./18-quality-check.sh full"
        echo
        _show_dashboard
    fi

    # Save current branch results
    mkdir -p "$_RESULTS_DIR"
    local save_file="$_RESULTS_DIR/baseline-${current}.txt"
    {
        echo "# Baseline for $current - $(date -Iseconds)"
        local j=0
        for check in "${checks[@]}"; do
            printf "%s|%s|%s\n" "$check" "${current_results[$j]}" "${_CHECK_DURATION[$check]:-0.000}"
            (( j++ )) || true
        done
    } > "$save_file"

    # If we are on the default branch, also save as that baseline
    if [[ "$current" == "$base" ]]; then
        cp "$save_file" "$baseline_file"
        sg_success "Baseline saved for $base."
    fi

    _OVERALL_EXIT=$current_exit
}

op_interactive() {
    sg_header "Quality Check"

    local lang
    lang="$(sg_detect_language)"
    local changed
    changed="$(_changed_files | wc -l | tr -d ' ')"
    local branch
    branch="$(sg_current_branch)"

    sg_info "Language: $lang | Branch: $branch | Changed files: $changed"
    echo

    if (( changed == 0 )); then
        sg_warn "No changed files detected."
        if ! sg_confirm "Run checks on entire codebase?"; then
            return 0
        fi
    fi

    # Show detected commands
    sg_info "Detected commands:"
    printf "  ${_C_DIM}Lint:     %s${_C_RESET}\n" "$(sg_detect_lint_cmd)"
    printf "  ${_C_DIM}Format:   %s${_C_RESET}\n" "$(sg_detect_fmt_cmd)"
    printf "  ${_C_DIM}Test:     %s${_C_RESET}\n" "$(sg_detect_test_cmd)"
    printf "  ${_C_DIM}Build:    %s${_C_RESET}\n" "$(sg_detect_build_cmd)"
    printf "  ${_C_DIM}Security: securegit scan${_C_RESET}\n"
    echo

    local choice
    choice="$(sg_menu "Select check mode" \
        "Quick (lint + format)" \
        "Full (all checks)" \
        "Custom (select checks)"
    )"

    case "$choice" in
        1)
            run_lint
            echo
            run_format
            ;;
        2)
            run_lint
            echo
            run_format
            echo
            run_test
            echo
            run_build
            echo
            run_scan
            ;;
        3)
            echo
            if sg_confirm "Run lint?"; then run_lint; echo; fi
            if sg_confirm "Run format check?"; then run_format; echo; fi
            if sg_confirm "Run tests?"; then run_test; echo; fi
            if sg_confirm "Run build?"; then run_build; echo; fi
            if sg_confirm "Run security scan?"; then run_scan; echo; fi
            ;;
    esac

    echo
    _show_dashboard
    _show_watch_hint
}

# ─── CLI Shortcut Handling ───────────────────────────────────────────────────

case "${1:-}" in
    quick)
        op_quick
        exit $_OVERALL_EXIT
        ;;
    full)
        op_full
        exit $_OVERALL_EXIT
        ;;
    lint)
        run_lint
        echo
        _show_dashboard
        exit $_OVERALL_EXIT
        ;;
    format)
        run_format
        echo
        _show_dashboard
        exit $_OVERALL_EXIT
        ;;
    test)
        run_test
        echo
        _show_dashboard
        exit $_OVERALL_EXIT
        ;;
    build)
        run_build
        echo
        _show_dashboard
        exit $_OVERALL_EXIT
        ;;
    scan)
        run_scan
        echo
        _show_dashboard
        exit $_OVERALL_EXIT
        ;;
    compare)
        op_compare
        exit $_OVERALL_EXIT
        ;;
    "")
        op_interactive
        exit $_OVERALL_EXIT
        ;;
    *)
        sg_die "Unknown command: $1. Use: quick, full, lint, format, test, build, scan, compare"
        ;;
esac
