#!/usr/bin/env nu

# Safe Merge Upstream Script
# Safely merges upstream changes while preserving local nu_* dependency versions

use lib/cargo_toml_diff.nu *

# Version check - mandatory for all plugin operations
def version_check [] {
    try {
        nu scripts/check_version.nu --quiet | ignore
    } catch {
        print "❌ Nushell version mismatch detected!"
        print "🔧 Run: nu scripts/check_version.nu --fix"
        exit 1
    }
}

# Load plugin registry
def load_registry [] {
    let registry_path = "etc/plugin_registry.toml"
    if not ($registry_path | path exists) {
        error make {msg: "Plugin registry not found. Please run from repository root directory."}
    }
    open $registry_path
}

# Load exclusions configuration
def load_exclusions [] {
    let exclude_path = "etc/upstream_exclude.toml"
    if ($exclude_path | path exists) {
        let config = open $exclude_path
        {
            all: ($config.exclude.plugins? | default []),
            check: ($config.exclude.check.plugins? | default []),
            merge: ($config.exclude.merge.plugins? | default []),
            patterns: ($config.exclude.patterns.plugins? | default [])
        }
    } else {
        {all: [], check: [], merge: [], patterns: []}
    }
}

# Check if plugin should be excluded
def is_plugin_excluded [plugin_name: string, operation: string, exclusions: record] {
    # Check direct exclusions
    if $plugin_name in $exclusions.all {
        return true
    }

    # Check operation-specific exclusions
    let op_exclusions = match $operation {
        "check" => $exclusions.check,
        "merge" => $exclusions.merge,
        _ => []
    }

    if $plugin_name in $op_exclusions {
        return true
    }

    # Check pattern exclusions
    for pattern in $exclusions.patterns {
        if ($plugin_name | str contains ($pattern | str replace "*" "")) {
            return true
        }
    }

    false
}

# Save plugin registry
def save_registry [registry: record] {
    $registry | to toml | save -f "etc/plugin_registry.toml"
}

# Get current date in ISO format
def current_date [] {
    date now | format date "%Y-%m-%d %H:%M:%S"
}

# Create backup branch
def create_backup [plugin_path: string] {
    cd $plugin_path

    let timestamp = date now | format date "%Y%m%d_%H%M%S"
    let backup_branch = $"backup_before_merge_($timestamp)"

    try {
        git checkout -b $backup_branch
        git checkout -  # Return to original branch
        print $"  📋 Created backup branch: ($backup_branch)"
        $backup_branch
    } catch {
        error make {msg: "Failed to create backup branch"}
    }
}

# Create temporary merge branch
def create_merge_branch [plugin_path: string] {
    cd $plugin_path

    let timestamp = date now | format date "%Y%m%d_%H%M%S"
    let merge_branch = $"temp_merge_($timestamp)"

    try {
        git checkout -b $merge_branch
        print $"  🔀 Created merge branch: ($merge_branch)"
        $merge_branch
    } catch {
        error make {msg: "Failed to create merge branch"}
    }
}

# Extract and preserve local nu dependencies
def preserve_local_nu_deps [plugin_path: string] {
    cd $plugin_path

    let cargo_toml = "Cargo.toml"
    if not ($cargo_toml | path exists) {
        error make {msg: "Cargo.toml not found"}
    }

    # Parse current local dependencies
    let local_cargo = parse_cargo_toml $cargo_toml
    let local_nu_deps = extract_nu_dependencies $local_cargo

    print $"  💾 Preserved ($local_nu_deps | columns | length) local nu_* dependencies"
    $local_nu_deps
}

# Restore local nu dependencies after merge
def restore_nu_deps [plugin_path: string, local_nu_deps: record] {
    cd $plugin_path

    let cargo_toml = "Cargo.toml"
    let content = open $cargo_toml

    # Update each nu dependency with local version
    mut updated_content = $content

    for dep in ($local_nu_deps | transpose key value) {
        let dep_name = $dep.key
        let dep_value = $dep.value

        # Update the dependency in the TOML structure
        $updated_content = ($updated_content | upsert dependencies.($dep_name) $dep_value)
    }

    # Save updated Cargo.toml
    $updated_content | to toml | save -f $cargo_toml
    print $"  🔧 Restored ($local_nu_deps | columns | length) local nu_* dependency versions"
}

# Perform the merge
def perform_merge [plugin_path: string, upstream_branch: string] {
    cd $plugin_path

    try {
        print $"  🔀 Merging upstream/($upstream_branch)..."
        git merge $"upstream/($upstream_branch)" --no-edit

        # Check if merge was successful
        let merge_status = git status --porcelain | lines
        if ($merge_status | length) > 0 {
            # Check if there are conflict markers
            let conflicts = $merge_status | where ($it | str starts-with "UU")
            if ($conflicts | length) > 0 {
                print $"  ❌ Merge conflicts detected"
                return false
            }
        }

        print $"  ✅ Merge completed successfully"
        true
    } catch {
        print $"  ❌ Merge failed"
        false
    }
}

# Test if the project compiles after merge
def test_compilation [plugin_path: string] {
    cd $plugin_path

    print $"  🧪 Testing compilation..."
    try {
        let result = cargo check --quiet
        print $"  ✅ Compilation successful"
        true
    } catch {
        print $"  ❌ Compilation failed"
        false
    }
}

# Run tests if they exist
def run_tests [plugin_path: string] {
    cd $plugin_path

    # Check if there are tests
    let test_files = glob "**/*test*.rs" | length
    if $test_files == 0 {
        print $"  ⏭️  No tests found, skipping"
        return true
    }

    print $"  🧪 Running tests..."
    try {
        cargo test --quiet
        print $"  ✅ All tests passed"
        true
    } catch {
        print $"  ❌ Tests failed"
        false
    }
}

# Rollback to original state
def rollback_changes [plugin_path: string, original_branch: string, merge_branch: string] {
    cd $plugin_path

    print $"  🔄 Rolling back changes..."
    try {
        git checkout $original_branch
        git branch -D $merge_branch
        print $"  ✅ Rollback completed"
    } catch {
        print $"  ⚠️  Rollback may have failed - manual intervention needed"
    }
}

# Apply successful merge to main branch
def apply_merge [plugin_path: string, original_branch: string, merge_branch: string] {
    cd $plugin_path

    print $"  ✅ Applying successful merge..."
    try {
        git checkout $original_branch
        git merge $merge_branch --no-edit
        git branch -D $merge_branch
        print $"  ✅ Merge applied to ($original_branch)"
        true
    } catch {
        print $"  ❌ Failed to apply merge"
        false
    }
}

# Process a single plugin merge
def process_plugin_merge [plugin_name: string, plugin_config: record, force: bool, exclusions: record] {
    print $"\n🔀 Processing merge for ($plugin_name)..."

    # Check if plugin is excluded
    if (is_plugin_excluded $plugin_name "merge" $exclusions) {
        print $"  🚫 Skipping ($plugin_name) - excluded from merging"
        return $plugin_config
    }

    let plugin_path = $plugin_config.local_path

    # Validation checks
    if ($plugin_config.upstream_url | is-empty) {
        print $"  ⏭️  Skipping ($plugin_name) - no upstream URL"
        return $plugin_config
    }

    if not ($plugin_path | path exists) {
        print $"  ❌ Plugin directory not found: ($plugin_path)"
        return $plugin_config
    }

    if ($plugin_config.status == "ok") and not $force {
        print $"  ✅ Plugin already marked as OK, use --force to merge anyway"
        return $plugin_config
    }

    if ($plugin_config.status == "local_only") {
        print $"  🏠 Local-only plugin, no upstream to merge"
        return $plugin_config
    }

    cd $plugin_path

    # Get current branch
    let original_branch = git branch --show-current | str trim

    # Check for uncommitted changes
    let uncommitted = git status --porcelain | lines
    if ($uncommitted | length) > 0 {
        print $"  ⚠️  Uncommitted changes detected. Please commit or stash them first."
        return $plugin_config
    }

    # Fetch latest upstream
    try {
        git fetch upstream $plugin_config.upstream_branch
    } catch {
        print $"  ❌ Failed to fetch upstream"
        return $plugin_config
    }

    # Check if there are actually changes to merge
    let changes = git diff $"HEAD..upstream/($plugin_config.upstream_branch)" --name-only | lines
    if ($changes | length) == 0 {
        print $"  ✅ No changes to merge"
        return ($plugin_config | upsert status "ok")
    }

    print $"  📋 Found ($changes | length) changed files: ($changes | str join ', ')"

    # Create backup branch
    let backup_branch = create_backup $plugin_path

    # Preserve local nu dependencies
    let local_nu_deps = preserve_local_nu_deps $plugin_path

    # Create merge branch
    let merge_branch = create_merge_branch $plugin_path

    # Perform merge
    let merge_success = perform_merge $plugin_path $plugin_config.upstream_branch

    if not $merge_success {
        print $"  ❌ Merge failed, cleaning up..."
        rollback_changes $plugin_path $original_branch $merge_branch
        return ($plugin_config | upsert status "conflict")
    }

    # Restore local nu dependencies
    restore_nu_deps $plugin_path $local_nu_deps

    # Test compilation
    let compile_success = test_compilation $plugin_path

    if not $compile_success {
        print $"  ❌ Compilation failed after merge, rolling back..."
        rollback_changes $plugin_path $original_branch $merge_branch
        return ($plugin_config | upsert status "error")
    }

    # Run tests
    let test_success = run_tests $plugin_path

    if not $test_success {
        print $"  ❌ Tests failed after merge, rolling back..."
        rollback_changes $plugin_path $original_branch $merge_branch
        return ($plugin_config | upsert status "error")
    }

    # Apply the successful merge
    let apply_success = apply_merge $plugin_path $original_branch $merge_branch

    if not $apply_success {
        print $"  ❌ Failed to apply merge"
        return ($plugin_config | upsert status "error")
    }

    # Update plugin configuration
    let upstream_commit = git rev-parse $"upstream/($plugin_config.upstream_branch)" | str trim
    print $"  ✅ Successfully merged upstream changes!"

    ($plugin_config
     | upsert status "ok"
     | upsert last_checked_commit $upstream_commit
     | upsert last_checked_date (current_date))
}

# Show merge preview
def show_merge_preview [plugin_name: string, plugin_config: record] {
    let plugin_path = $plugin_config.local_path

    if not ($plugin_path | path exists) {
        print $"Plugin directory not found: ($plugin_path)"
        return
    }

    cd $plugin_path

    print $"🔍 Merge preview for ($plugin_name):"
    print "=" * 50

    try {
        let changes = git diff $"HEAD..upstream/($plugin_config.upstream_branch)" --name-only | lines
        print $"Files that will be changed: ($changes | length)"
        for file in $changes {
            print $"  📄 ($file)"
        }

        print "\nSample diff (first 20 lines):"
        git diff $"HEAD..upstream/($plugin_config.upstream_branch)" | lines | first 20 | each {|line| print $"  ($line)"}

        if ($changes | length) > 20 {
            print "  ... (output truncated)"
        }
    } catch {
        print "Failed to generate preview"
    }
}

# Main function
def main [
    plugin?: string              # Specific plugin to merge
    --force (-f)                # Force merge even if status is OK
    --preview (-p)              # Show merge preview only
    --all (-a)                  # Process all pending plugins
    --help (-h)                 # Show help
] {
    # Mandatory version check before any plugin operations
    version_check

    if $help {
        print "Safe Merge Upstream - Nushell Plugin Upstream Merger"
        print ""
        print "USAGE:"
        print "  nu safe_merge_upstream.nu [OPTIONS] [PLUGIN]"
        print ""
        print "OPTIONS:"
        print "  --force, -f        Force merge even if plugin status is OK"
        print "  --preview, -p      Show merge preview without performing merge"
        print "  --all, -a          Process all plugins with pending status"
        print "  --help, -h         Show this help message"
        print ""
        print "EXAMPLES:"
        print "  nu safe_merge_upstream.nu highlight         # Merge specific plugin"
        print "  nu safe_merge_upstream.nu --preview highlight  # Preview changes"
        print "  nu safe_merge_upstream.nu --all            # Merge all pending"
        print "  nu safe_merge_upstream.nu --force highlight   # Force merge"
        return
    }

    # Ensure we're in the repository root directory
    if not ("nu_plugin_clipboard" | path exists) {
        error make {msg: "Please run this script from the nushell-plugins repository root directory"}
    }

    # Load registry and exclusions
    let registry = load_registry
    let exclusions = load_exclusions

    # Determine which plugins to process
    let plugins_to_process = if $all {
        $registry.plugins
        | transpose key value
        | where $it.value.status in ["pending", "error", "conflict"]
        # Filter out excluded plugins
        | where not (is_plugin_excluded $it.key "merge" $exclusions)
    } else if ($plugin | is-not-empty) {
        if $plugin in ($registry.plugins | columns) {
            [{key: $plugin, value: ($registry.plugins | get $plugin)}]
        } else {
            error make {msg: $"Plugin '($plugin)' not found in registry"}
        }
    } else {
        error make {msg: "Please specify a plugin name or use --all flag"}
    }

    if ($plugins_to_process | length) == 0 {
        print "No plugins found to process"
        return
    }

    # Preview mode
    if $preview {
        for plugin_entry in $plugins_to_process {
            show_merge_preview $plugin_entry.key $plugin_entry.value
            print ""
        }
        return
    }

    # Confirmation for multiple plugins
    if ($plugins_to_process | length) > 1 {
        print $"About to process ($plugins_to_process | length) plugins:"
        for plugin_entry in $plugins_to_process {
            print $"  - ($plugin_entry.key) (status: ($plugin_entry.value.status))"
        }
        print ""
        let confirm = input "Continue? [y/N]: "
        if ($confirm | str downcase) != "y" {
            print "Aborted"
            return
        }
    }

    # Process plugins
    mut updated_registry = $registry

    for plugin_entry in $plugins_to_process {
        let plugin_name = $plugin_entry.key
        let plugin_config = $plugin_entry.value

        let updated_config = process_plugin_merge $plugin_name $plugin_config $force $exclusions
        $updated_registry = ($updated_registry | upsert plugins.($plugin_name) $updated_config)
    }

    # Save updated registry
    save_registry $updated_registry

    print $"\n✅ Merge process completed!"
    print "📊 Run 'nu plugin_status.nu' to see updated status"
}

if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") {
    main
}