#!/bin/bash
# scripts/quality_check.sh
#
# Copyright (C) 2025 陳鈞
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# Quality Assurance Check Script for SubX
# This script performs comprehensive code quality checks including compilation,
# formatting, linting, documentation, and testing

set -e

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"

# Initialize verbose mode
VERBOSE=false

# Initialize nextest profile
NEXTEST_PROFILE="default"

# Initialize full tests mode
FULL_TESTS=false

# Initialize spec-governance-only mode
CHECK_SPEC_GOVERNANCE_ONLY=false

# Governance failure state: any failed predicate makes the script exit 2
# (distinct from the generic failure exit 1) so a caller can tell a
# spec/document governance defect apart from a code-quality failure.
GOVERNANCE_FAILED=false

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Usage information
usage() {
    echo "Usage: $0 [options]"
    echo "Options:"
    echo "  -v, --verbose            Show verbose output"
    echo "  -p, --profile PROFILE    Set nextest profile (default: default)"
    echo "  --full                   Run full tests including slow tests (sets profile to full and enables slow-tests feature)"
    echo "  --check-spec-governance  Run only the spec-governance drift check (predicates over the two OpenSpec trees and the shared AGENTS.md region; exit 2 on any governance failure)"
    echo "  -h, --help               Show this help"
    echo ""
    echo "This script performs comprehensive quality assurance checks including:"
    echo "  - Code compilation and formatting checks"
    echo "  - Clippy linting and code quality analysis"
    echo "  - Documentation generation and coverage"
    echo "  - Documentation examples testing"
    echo "  - Unit and integration tests"
    echo ""
    echo "Available nextest profiles: default, ci, quick, full"
    echo ""
    echo "Examples:"
    echo "  $0                       Run all quality checks with standard output (excludes slow tests)"
    echo "  $0 -v                    Run all quality checks with verbose output (excludes slow tests)"
    echo "  $0 -p ci                 Run with CI profile"
    echo "  $0 --full                Run full tests including slow tests (~90 vs ~143 seconds)"
    echo "  $0 -v --full             Run full tests with verbose output"
    echo "  $0 --check-spec-governance  Run only the spec-governance drift check"
}

# Parse command line arguments
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
        -v | --verbose)
            VERBOSE=true
            shift
            ;;
        -p | --profile)
            if [[ -z "$2" ]]; then
                echo -e "${RED}Error: --profile requires a value${NC}" >&2
                usage >&2
                exit 1
            fi
            # Validate profile value
            case "$2" in
                default|ci|quick|full)
                    NEXTEST_PROFILE="$2"
                    ;;
                *)
                    echo -e "${RED}Error: Invalid profile '$2'. Available profiles: default, ci, quick, full${NC}" >&2
                    exit 1
                    ;;
            esac
            shift 2
            ;;
        --full)
            FULL_TESTS=true
            NEXTEST_PROFILE="full"
            shift
            ;;
        --check-spec-governance)
            CHECK_SPEC_GOVERNANCE_ONLY=true
            shift
            ;;
        -h | --help)
            usage
            exit 0
            ;;
        *)
            echo -e "${RED}Error: Unknown option $1${NC}" >&2
            usage >&2
            exit 1
            ;;
        esac
    done
}

# Function to print colored output
print_status() {
    local color=$1
    local message=$2
    echo -e "${color}${message}${NC}"
}

# Function to check command success
check_result() {
    local exit_code=$1
    local test_name=$2
    
    if [ "${exit_code}" -eq 0 ]; then
        print_status "$GREEN" "✅ $test_name: Passed"
        return 0
    else
        print_status "$RED" "❌ $test_name: Failed"
        return 1
    fi
}

# Initialize counters
total_checks=0
passed_checks=0
failed_checks=0

run_check() {
    local check_name=$1
    local command=$2
    
    total_checks=$((total_checks + 1))
    if [[ "${VERBOSE}" == "true" ]]; then
        print_status "$BLUE" "\n🔍 Running check: $check_name"
    fi
    
    if eval "$command"; then
        check_result 0 "$check_name"
        passed_checks=$((passed_checks + 1))
        return 0
    else
        check_result $? "$check_name"
        failed_checks=$((failed_checks + 1))
        return 1
    fi
}

# Function to run tests with conditional output (hide output unless failure or verbose)
run_test_with_conditional_output() {
    local test_name=$1
    local command=$2
    
    total_checks=$((total_checks + 1))
    
    if [[ "${VERBOSE}" == "true" ]]; then
        # Verbose mode: show all output
        print_status "$BLUE" "\n🔍 Running check: $test_name"
        if eval "$command"; then
            check_result 0 "$test_name"
            passed_checks=$((passed_checks + 1))
            return 0
        else
            check_result $? "$test_name"
            failed_checks=$((failed_checks + 1))
            return 1
        fi
    else
        # Non-verbose mode: capture output and only show on failure
        local temp_output
        temp_output=$(mktemp)
        
        if eval "$command" > "$temp_output" 2>&1; then
            # Test passed - only show success message
            check_result 0 "$test_name"
            passed_checks=$((passed_checks + 1))
            rm -f "$temp_output"
            return 0
        else
            # Test failed - show failure message and output
            check_result $? "$test_name"
            echo ""
            echo "=== Test Output ==="
            cat "$temp_output"
            echo "==================="
            failed_checks=$((failed_checks + 1))
            rm -f "$temp_output"
            return 1
        fi
    fi
}

# ---------------------------------------------------------------------------
# Spec-governance drift check (spec-governance capability; design.md
# Decision 7 of refresh-docs-for-two-crate-architecture).
#
# Predicates:
#   1. No capability directory name appears in both openspec/specs/ and
#      subx-core/openspec/specs/ unless it is recorded in
#      openspec/split-capabilities.txt.
#   2. Every name recorded in openspec/split-capabilities.txt exists in
#      BOTH trees — a name in one tree only is a split that lost a half.
#   3. The SHARED_CONVENTIONS region of AGENTS.md and subx-core/AGENTS.md
#      is byte-identical (marker-inclusive; the tool-managed CODEGRAPH
#      block lives outside the region in both files).
#
# Extraction writes marker-inclusive content straight to temporary files
# (no command substitution, which would strip trailing newlines) with
# carriage returns stripped, so the compared bytes are LF-normalised
# content — the same contract as Extract-SharedRegion in
# quality_check.ps1, whose [IO.File]::ReadAllLines drops CR as it splits
# lines. The comparison is a raw-byte cmp of those normalised bytes; a
# textual diff runs only as a diagnostic after a byte mismatch.
# ---------------------------------------------------------------------------
extract_shared_region() {
    local file=$1
    local out=$2
    local starts ends start_line end_line clean
    # LF-normalise once up front so marker matching and extraction see the
    # same content the PowerShell port sees (its [IO.File]::ReadAllLines
    # splits on CRLF or LF and re-joins with LF).
    clean=$(mktemp)
    tr -d '\r' < "$file" > "$clean"
    starts=$(grep -c '^<!-- SHARED_CONVENTIONS_START -->$' "$clean" || true)
    ends=$(grep -c '^<!-- SHARED_CONVENTIONS_END -->$' "$clean" || true)
    if [ "${starts}" -ne 1 ] || [ "${ends}" -ne 1 ]; then
        rm -f "$clean"
        print_status "$RED" "   $file: expected exactly one SHARED_CONVENTIONS_START and one SHARED_CONVENTIONS_END marker, found ${starts} start / ${ends} end"
        return 1
    fi
    start_line=$(grep -n '^<!-- SHARED_CONVENTIONS_START -->$' "$clean" | cut -d: -f1)
    end_line=$(grep -n '^<!-- SHARED_CONVENTIONS_END -->$' "$clean" | cut -d: -f1)
    if [ "${end_line}" -le "${start_line}" ]; then
        rm -f "$clean"
        print_status "$RED" "   $file: SHARED_CONVENTIONS_END appears before SHARED_CONVENTIONS_START"
        return 1
    fi
    sed -n '/^<!-- SHARED_CONVENTIONS_START -->$/,/^<!-- SHARED_CONVENTIONS_END -->$/p' "$clean" > "$out"
    rm -f "$clean"
}

check_spec_governance() {
    local root_specs="openspec/specs"
    local core_specs="subx-core/openspec/specs"
    local record="openspec/split-capabilities.txt"
    local ok=true

    # Predicate inputs require the submodule's OpenSpec tree. A missing
    # submodule already fails the build at manifest-parse time, so skip
    # with a warning rather than adding a second, vaguer failure.
    if [ ! -d "${core_specs}" ]; then
        print_status "$YELLOW" "⚠️  Spec Governance Check: Skipped — ${core_specs}/ not found (submodule not initialised?)"
        return 0
    fi
    if [ ! -f "${record}" ]; then
        print_status "$RED" "❌ Spec Governance Check: ${record} not found"
        return 1
    fi

    # Predicate 1: duplicated capability names must be recorded splits.
    local offenders=""
    local dir name
    for dir in "${core_specs}"/*/; do
        [ -d "${dir}" ] || continue
        name=$(basename "${dir}")
        if [ -d "${root_specs}/${name}" ] && ! grep -qxF "${name}" "${record}"; then
            offenders="${offenders}${name}"$'\n'
        fi
    done
    if [ -n "${offenders}" ]; then
        ok=false
        print_status "$RED" "❌ Predicate 1: capability present in both OpenSpec trees but not recorded in ${record}:"
        while IFS= read -r name; do
            [ -n "${name}" ] && print_status "$RED" "   ${name}"
        done <<< "${offenders}"
    fi

    # Predicate 2: every recorded split exists in both trees.
    while IFS= read -r name; do
        [ -z "${name}" ] && continue
        local missing=""
        [ -d "${root_specs}/${name}" ] || missing="${missing} ${root_specs}"
        [ -d "${core_specs}/${name}" ] || missing="${missing} ${core_specs}"
        if [ -n "${missing}" ]; then
            ok=false
            print_status "$RED" "❌ Predicate 2: recorded split '${name}' is missing from:${missing}"
        fi
    done < "${record}"

    # Predicate 3: byte-identical shared conventions region.
    local region_root region_core
    region_root=$(mktemp)
    region_core=$(mktemp)
    if extract_shared_region "AGENTS.md" "${region_root}" && \
       extract_shared_region "subx-core/AGENTS.md" "${region_core}"; then
        if ! cmp -s "${region_root}" "${region_core}"; then
            ok=false
            print_status "$RED" "❌ Predicate 3: shared conventions region differs between:"
            print_status "$RED" "   AGENTS.md and subx-core/AGENTS.md — diff of the extracted regions:"
            diff "${region_root}" "${region_core}" || true
        fi
    else
        ok=false
        print_status "$RED" "❌ Predicate 3: SHARED_CONVENTIONS markers malformed in AGENTS.md and/or subx-core/AGENTS.md"
    fi
    rm -f "${region_root}" "${region_core}"

    [ "${ok}" = true ]
}

# Main function
main() {
    parse_args "$@"

    # Standalone governance mode: run only the drift predicates.
    if [[ "${CHECK_SPEC_GOVERNANCE_ONLY}" == "true" ]]; then
        echo "🔍 SubX Spec-Governance Drift Check Starting..."
        echo "========================================"
        if check_spec_governance; then
            print_status "$GREEN" "\n🎉 Spec-governance drift check passed!"
            exit 0
        else
            print_status "$RED" "\n⚠️  Spec-governance drift check FAILED (exit 2)"
            exit 2
        fi
    fi

    # Show startup message
    echo "🔍 SubX Quality Assurance Check Starting..."
    echo "========================================"
    echo "🔧 Using nextest profile: ${NEXTEST_PROFILE}"
    if [[ "${FULL_TESTS}" == "true" ]]; then
        echo "⚡ Full tests mode: Including slow tests (~143s vs ~90s)"
    else
        echo "🚀 Fast tests mode: Excluding slow tests (~90s vs ~143s)"
        echo "   Use --full to include slow tests"
    fi
    echo "========================================"
    
    # 1. Code compilation check
    local cargo_features=""
    if [[ "${FULL_TESTS}" == "true" ]]; then
        cargo_features="--features slow-tests"
    fi
    
    if [[ "${VERBOSE}" == "true" ]]; then
        run_check "Code Compilation Check" "cargo check --workspace --all-features ${cargo_features}"
    else
        run_check "Code Compilation Check" "cargo check --workspace --all-features ${cargo_features} --quiet"
    fi

    # 2. Code formatting check
    run_check "Code Formatting Check" "cargo fmt -- --check"

    # 3. Clippy linting check
    if [[ "${VERBOSE}" == "true" ]]; then
        run_check "Clippy Code Quality Check" "cargo clippy --workspace --all-features ${cargo_features} -- -D warnings"
    else
        run_check "Clippy Code Quality Check" "cargo clippy --workspace --all-features ${cargo_features} --quiet -- -D warnings"
    fi

    # 4. Documentation generation check
    if [[ "${VERBOSE}" == "true" ]]; then
        print_status "$BLUE" "\n🔍 Running check: Documentation Generation Check"
    fi
    total_checks=$((total_checks + 1))

    # Create temporary file for documentation output
    local doc_output
    doc_output=$(mktemp)

    if [[ "${VERBOSE}" == "true" ]]; then
        cargo doc --workspace --all-features ${cargo_features} --no-deps --document-private-items 2>&1 | tee "$doc_output"
    else
        cargo doc --workspace --all-features ${cargo_features} --no-deps --document-private-items > "$doc_output" 2>&1
    fi

    # Check for critical errors (excluding known lint warnings).
    # Match only genuine rustc/cargo error lines (which start with `error`),
    # so the future-incompat warning about the `proc-macro-error2` package
    # name (which merely contains the substring "error") does not trigger a
    # false-positive failure.
    if grep -E "^error(\[|:)" "$doc_output" | grep -v "warning\[E0602\]: unknown lint"; then
        print_status "$RED" "❌ Documentation Generation Check: Critical errors found"
        failed_checks=$((failed_checks + 1))
    else
        # Count warnings (excluding known lint warnings)
        warning_lines=$(grep -E "(warning)" "$doc_output" | grep -v "warning\[E0602\]: unknown lint" || true)
        if [ -n "$warning_lines" ]; then
            warning_count=$(echo "$warning_lines" | wc -l)
        else
            warning_count=0
        fi
        if [ "$warning_count" -gt 0 ]; then
            print_status "$YELLOW" "⚠️  Documentation Generation Check: Passed (with $warning_count warnings)"
        else
            print_status "$GREEN" "✅ Documentation Generation Check: Passed"
        fi
        passed_checks=$((passed_checks + 1))
    fi

    # Clean up documentation output file
    rm -f "$doc_output"

    # 5. Documentation examples test
    run_test_with_conditional_output "Documentation Examples Test" "cargo test --doc --all-features ${cargo_features}"

    # 6. Documentation coverage check  
    if [[ "${VERBOSE}" == "true" ]]; then
        print_status "$BLUE" "\n🔍 Running check: Documentation Coverage Check"
    fi
    total_checks=$((total_checks + 1))

    # Check for missing documentation (allow warnings, don't fail build)
    if [[ "${VERBOSE}" == "true" ]]; then
        missing_docs_output=$(cargo clippy --all-features ${cargo_features} -- -W missing_docs 2>&1 | grep -v "warning\[E0602\]" | grep "missing documentation" || true)
    else
        missing_docs_output=$(cargo clippy --all-features ${cargo_features} --quiet -- -W missing_docs 2>&1 | grep -v "warning\[E0602\]" | grep "missing documentation" || true)
    fi

    if [ -n "$missing_docs_output" ]; then
        if [ -n "$missing_docs_output" ]; then
            missing_count=$(echo "$missing_docs_output" | wc -l)
        else
            missing_count=0
        fi
        print_status "$YELLOW" "⚠️  Documentation Coverage Check: Found $missing_count items missing documentation"
        
        # Only show details in verbose mode
        if [[ "${VERBOSE}" == "true" ]]; then
            # Only show first 5 items to avoid overwhelming output
            echo "$missing_docs_output" | head -5
            if [ "$missing_count" -gt 5 ]; then
                echo "... (showing first 5 of $missing_count items)"
            fi
            print_status "$BLUE" "ℹ️  These are improvement suggestions and won't affect build success"
        fi
    else
        print_status "$GREEN" "✅ Documentation Coverage Check: All public APIs have documentation"
    fi
    passed_checks=$((passed_checks + 1))

    # 7. Unit tests
    local nextest_features=""
    if [[ "${FULL_TESTS}" == "true" ]]; then
        nextest_features="--features slow-tests"
    fi
    run_test_with_conditional_output "Unit Tests" "cargo nextest run --workspace --profile ${NEXTEST_PROFILE} ${nextest_features} -E 'kind(lib)' --ignore-default-filter"

    # 8. Integration tests  
    run_test_with_conditional_output "Integration Tests" "cargo nextest run --workspace --profile ${NEXTEST_PROFILE} ${nextest_features} --ignore-default-filter"

    # 9. Spec-governance drift check (two OpenSpec trees + shared AGENTS.md region)
    total_checks=$((total_checks + 1))
    if check_spec_governance; then
        print_status "$GREEN" "✅ Spec Governance Check: Passed"
        passed_checks=$((passed_checks + 1))
    else
        failed_checks=$((failed_checks + 1))
        GOVERNANCE_FAILED=true
    fi

    # Cleanup
    # (Temporary files are cleaned up in their respective sections)

    # Summary
    echo ""
    echo "========================================"
    print_status "$BLUE" "📊 Quality Assurance Check Summary"
    echo "========================================"
    print_status "$GREEN" "✅ Passed checks: $passed_checks"
    print_status "$RED" "❌ Failed checks: $failed_checks"  
    print_status "$BLUE" "📋 Total checks: $total_checks"

    if [ $failed_checks -eq 0 ]; then
        print_status "$GREEN" "\n🎉 All quality assurance checks passed!"
        exit 0
    elif [ "${GOVERNANCE_FAILED}" = true ]; then
        print_status "$RED" "\n⚠️  Spec-governance predicates failed — see 'Spec Governance Check' output above (exit 2)"
        exit 2
    else
        print_status "$RED" "\n⚠️  Some checks failed, please review the error messages above"
        exit 1
    fi
}

# Execute main program
main "$@"
