#!/bin/sh
# Generate debian/changelog from git history using annotated tags
#
# This script creates a Debian changelog by:
# 1. Finding all annotated tags (releases)
# 2. Extracting commit messages between tags
# 3. Formatting according to Debian changelog standards
# 4. Including proper timestamps and maintainer info
#
# Usage: ./scripts/generate-changelog.sh [output_file] [package_name] [maintainer_name] [maintainer_email]

set -e

# Default values
OUTPUT_FILE="${1:-debian/changelog}"
PACKAGE_NAME="${2:-rpi-systemd-gpio}"
MAINTAINER_NAME="${3:-Tom Dewey}"
MAINTAINER_EMAIL="${4:-tom.dewey@raspberrypi.com}"

# Check if we're in a git repository
if ! git rev-parse --git-dir >/dev/null 2>&1; then
    echo "Error: Not in a git repository" >&2
    exit 1
fi

# Function to format date for Debian changelog (RFC 2822 format)
format_debian_date() {
    local iso_date="$1"
    # Convert git date to RFC 2822 format
    # Input: "2024-01-15 10:30:45 +0000"
    # Output: "Mon, 15 Jan 2024 10:30:45 +0000"
    date -d "$iso_date" "+%a, %d %b %Y %H:%M:%S %z" 2>/dev/null || \
    date -j -f "%Y-%m-%d %H:%M:%S %z" "$iso_date" "+%a, %d %b %Y %H:%M:%S %z" 2>/dev/null || \
    date "+%a, %d %b %Y %H:%M:%S %z"
}

# Function to categorize commit message
categorize_commit() {
    local subject="$1"
    local category="change"
    local cleaned_subject="$subject"
    
    # Remove conventional commit prefixes and categorize
    case "$subject" in
        feat\(*|feat:*|feat!:*)
            category="feature"
            cleaned_subject=$(echo "$subject" | sed -E 's/^feat(\([^)]+\))?!?:\s*//')
            ;;
        fix\(*|fix:*|fix!:*)
            category="bugfix"
            cleaned_subject=$(echo "$subject" | sed -E 's/^fix(\([^)]+\))?!?:\s*//')
            ;;
        docs\(*|docs:*|doc\(*|doc:*|docs!:*|doc!:*)
            category="documentation"
            cleaned_subject=$(echo "$subject" | sed -E 's/^docs?(\([^)]+\))?!?:\s*//')
            ;;
        test\(*|test:*|test!:*)
            category="testing"
            cleaned_subject=$(echo "$subject" | sed -E 's/^test(\([^)]+\))?!?:\s*//')
            ;;
        build\(*|build:*|ci\(*|ci:*|build!:*|ci!:*)
            category="build"
            cleaned_subject=$(echo "$subject" | sed -E 's/^(build|ci)(\([^)]+\))?!?:\s*//')
            ;;
        chore\(*|chore:*|chore!:*)
            category="maintenance"
            cleaned_subject=$(echo "$subject" | sed -E 's/^chore(\([^)]+\))?!?:\s*//')
            ;;
        refactor\(*|refactor:*|refactor!:*)
            category="refactoring"
            cleaned_subject=$(echo "$subject" | sed -E 's/^refactor(\([^)]+\))?!?:\s*//')
            ;;
        perf\(*|perf:*|perf!:*)
            category="performance"
            cleaned_subject=$(echo "$subject" | sed -E 's/^perf(\([^)]+\))?!?:\s*//')
            ;;
        style\(*|style:*|style!:*)
            category="style"
            cleaned_subject=$(echo "$subject" | sed -E 's/^style(\([^)]+\))?!?:\s*//')
            ;;
        security\(*|security:*|security!:*)
            category="security"
            cleaned_subject=$(echo "$subject" | sed -E 's/^security(\([^)]+\))?!?:\s*//')
            ;;
    esac
    
    # Capitalize first letter and ensure it ends with a period
    if [ -n "$cleaned_subject" ]; then
        first_char=$(echo "$cleaned_subject" | cut -c1 | tr '[:lower:]' '[:upper:]')
        rest=$(echo "$cleaned_subject" | cut -c2-)
        cleaned_subject="$first_char$rest"
        
        case "$cleaned_subject" in
            *.) ;;
            *) cleaned_subject="$cleaned_subject." ;;
        esac
    fi
    
    echo "$category|$cleaned_subject"
}

# Function to determine urgency based on commit messages
determine_urgency() {
    local commits="$1"
    
    # High urgency: actual security fixes, CVEs, critical bugs, hotfixes
    if echo "$commits" | grep -qi -E "(cve-|vulnerability|critical|urgent|hotfix|security fix|security patch)"; then
        echo "high"
    # Medium urgency: everything else (features, enhancements, regular fixes)
    else
        echo "medium"
    fi
}

# Function to generate changelog entry for a version
generate_changelog_entry() {
    local version="$1"
    local commit_range="$2"
    local tag_date="$3"
    local urgency="$4"
    local tag_name="$5"
    
    # Get commits in the range
    local commits
    if [ -n "$commit_range" ]; then
        commits=$(git log --format="%s" --reverse "$commit_range" 2>/dev/null || true)
    else
        commits=$(git log --format="%s" --reverse 2>/dev/null || true)
    fi
    
    # Get annotated tag message if this is a tag-based entry
    local tag_message=""
    if [ -n "$tag_name" ]; then
        # Get the tag annotation, skip the first line if it's just the release title
        tag_message=$(git tag -l --format='%(contents)' "$tag_name" 2>/dev/null | sed '/^$/d' || true)
        # If the first line looks like "Release vX.X.X", skip it and any following blank lines
        tag_message=$(echo "$tag_message" | sed '/^[Rr]elease v\?[0-9]/d' | sed '/^$/d' || true)
    fi
    
    if [ -z "$commits" ] && [ -z "$tag_message" ]; then
        return
    fi
    
    # Start the changelog entry
    echo "$PACKAGE_NAME ($version) unstable; urgency=$urgency"
    echo ""
    
    # Create temporary files for each category
    temp_dir=$(mktemp -d)
    security_file="$temp_dir/security"
    bugfix_file="$temp_dir/bugfix"
    feature_file="$temp_dir/feature"
    performance_file="$temp_dir/performance"
    refactoring_file="$temp_dir/refactoring"
    documentation_file="$temp_dir/documentation"
    testing_file="$temp_dir/testing"
    build_file="$temp_dir/build"
    style_file="$temp_dir/style"
    maintenance_file="$temp_dir/maintenance"
    change_file="$temp_dir/change"
    
    # First, add tag message if available (high priority)
    tag_notes_file="$temp_dir/tag_notes"
    if [ -n "$tag_message" ]; then
        echo "$tag_message" | while IFS= read -r line; do
            if [ -n "$line" ]; then
                # Clean up and format tag message lines
                cleaned_line=$(echo "$line" | sed 's/^[[:space:]]*[-*][[:space:]]*//' | sed 's/^[[:space:]]*//')
                if [ -n "$cleaned_line" ]; then
                    # Ensure it starts with capital and ends with period
                    first_char=$(echo "$cleaned_line" | cut -c1 | tr '[:lower:]' '[:upper:]')
                    rest=$(echo "$cleaned_line" | cut -c2-)
                    formatted_line="$first_char$rest"
                    case "$formatted_line" in
                        *.) ;;
                        *) formatted_line="$formatted_line." ;;
                    esac
                    echo "$formatted_line" >> "$tag_notes_file"
                fi
            fi
        done
    fi
    
    # Then process individual commits
    if [ -n "$commits" ]; then
        echo "$commits" | while IFS= read -r commit; do
            if [ -n "$commit" ]; then
                result=$(categorize_commit "$commit")
                category=$(echo "$result" | cut -d'|' -f1)
                message=$(echo "$result" | cut -d'|' -f2-)
                
                case "$category" in
                    security) echo "$message" >> "$security_file" ;;
                    bugfix) echo "$message" >> "$bugfix_file" ;;
                    feature) echo "$message" >> "$feature_file" ;;
                    performance) echo "$message" >> "$performance_file" ;;
                    refactoring) echo "$message" >> "$refactoring_file" ;;
                    documentation) echo "$message" >> "$documentation_file" ;;
                    testing) echo "$message" >> "$testing_file" ;;
                    build) echo "$message" >> "$build_file" ;;
                    style) echo "$message" >> "$style_file" ;;
                    maintenance) echo "$message" >> "$maintenance_file" ;;
                    *) echo "$message" >> "$change_file" ;;
                esac
            fi
        done
    fi
    
    # First, output tag notes (release notes)
    if [ -f "$tag_notes_file" ] && [ -s "$tag_notes_file" ]; then
        while IFS= read -r line; do
            if [ -n "$line" ]; then
                echo "  * $line"
            fi
        done < "$tag_notes_file"
        
        # Add blank line separator if we have both tag notes and commits
        has_commits=false
        for category in security bugfix feature performance refactoring documentation testing build style maintenance change; do
            case "$category" in
                security) file="$security_file" ;;
                bugfix) file="$bugfix_file" ;;
                feature) file="$feature_file" ;;
                performance) file="$performance_file" ;;
                refactoring) file="$refactoring_file" ;;
                documentation) file="$documentation_file" ;;
                testing) file="$testing_file" ;;
                build) file="$build_file" ;;
                style) file="$style_file" ;;
                maintenance) file="$maintenance_file" ;;
                change) file="$change_file" ;;
            esac
            if [ -f "$file" ] && [ -s "$file" ]; then
                has_commits=true
                break
            fi
        done
        
        if [ "$has_commits" = "true" ]; then
            echo ""
        fi
    fi
    
    # Then output individual commits in priority order
    for category in security bugfix feature performance refactoring documentation testing build style maintenance change; do
        case "$category" in
            security) file="$security_file" ;;
            bugfix) file="$bugfix_file" ;;
            feature) file="$feature_file" ;;
            performance) file="$performance_file" ;;
            refactoring) file="$refactoring_file" ;;
            documentation) file="$documentation_file" ;;
            testing) file="$testing_file" ;;
            build) file="$build_file" ;;
            style) file="$style_file" ;;
            maintenance) file="$maintenance_file" ;;
            change) file="$change_file" ;;
        esac
        
        if [ -f "$file" ] && [ -s "$file" ]; then
            while IFS= read -r line; do
                if [ -n "$line" ]; then
                    echo "  * $line"
                fi
            done < "$file"
        fi
    done
    
    # Clean up temporary files
    rm -rf "$temp_dir"
    
    # Format the date and add maintainer line
    echo ""
    formatted_date=$(format_debian_date "$tag_date")
    echo " -- $MAINTAINER_NAME <$MAINTAINER_EMAIL>  $formatted_date"
    echo ""
}

# Main function
main() {
    # Get all annotated tags sorted by version (newest first)
    tags=$(git tag -l --sort=-version:refname 2>/dev/null | while read -r tag; do
        if [ -n "$tag" ] && git cat-file -t "$tag" 2>/dev/null | grep -q "^tag$"; then
            commit_hash=$(git rev-list -n 1 "$tag" 2>/dev/null)
            commit_date=$(git show -s --format=%ci "$commit_hash" 2>/dev/null)
            echo "$tag|$commit_hash|$commit_date"
        fi
    done)
    
    if [ -z "$tags" ]; then
        echo "No annotated tags found. Creating entry from all commits." >&2
        # Get all commits
        all_commits=$(git log --format="%s" --reverse 2>/dev/null || true)
        if [ -n "$all_commits" ]; then
            current_date=$(date "+%Y-%m-%d %H:%M:%S %z")
            urgency=$(determine_urgency "$all_commits")
            generate_changelog_entry "1.0.0" "" "$current_date" "$urgency" ""
        else
            # Fallback entry
            echo "$PACKAGE_NAME (1.0.0) unstable; urgency=medium"
            echo ""
            echo "  * Initial release."
            echo ""
            formatted_date=$(date "+%a, %d %b %Y %H:%M:%S %z")
            echo " -- $MAINTAINER_NAME <$MAINTAINER_EMAIL>  $formatted_date"
            echo ""
        fi
        return
    fi
    
    # Process each tag (from newest to oldest)
    prev_tag=""
    echo "$tags" | while IFS='|' read -r tag commit_hash commit_date; do
        if [ -n "$tag" ]; then
            # Clean version (remove 'v' prefix if present)
            version=$(echo "$tag" | sed 's/^v//')
            
            # Determine commit range
            if [ -n "$prev_tag" ]; then
                # For older releases: show commits that are new since the previous tag
                commit_range="$prev_tag..$tag"
            else
                # For newest release: show commits since the previous tag (or all if first tag)
                # Find the previous tag in chronological order
                next_older_tag=$(echo "$tags" | awk -F'|' -v current="$tag" '
                    found==1 { print $1; exit }
                    $1==current { found=1 }
                ')
                if [ -n "$next_older_tag" ]; then
                    commit_range="$next_older_tag..$tag"
                else
                    # This is the oldest tag, show all commits up to it
                    commit_range="$tag"
                fi
            fi
            
            # Get commits for urgency determination
            range_commits=$(git log --format="%s" "$commit_range" 2>/dev/null || true)
            urgency=$(determine_urgency "$range_commits")
            
            # Generate the changelog entry
            generate_changelog_entry "$version" "$commit_range" "$commit_date" "$urgency" "$tag"
            
            prev_tag="$tag"
        fi
    done
}

# Run the main function and write to output file
if [ "$1" = "--dry-run" ] || [ "$1" = "-n" ]; then
    main
else
    # Create output directory if it doesn't exist
    mkdir -p "$(dirname "$OUTPUT_FILE")"
    
    # Generate changelog
    main > "$OUTPUT_FILE"
    echo "Changelog written to $OUTPUT_FILE"
fi
