#!/usr/bin/env nu

# Platform distribution preparation tool - prepares platform services for distribution
#
# Prepares:
# - Rust platform binaries (orchestrator, control-center, etc.)
# - Container images and deployment configurations
# - Service definitions and systemd units
# - Platform-specific installation packages
# - Cross-compilation for multiple architectures

use std log

def main [
    --source-root: string = ""                  # Source root directory (auto-detected if empty)
    --output-dir: string = "dist/platform"     # Output directory for platform distribution
    --target-platforms: string = "linux-amd64,macos-amd64,windows-amd64" # Target platforms
    --build-mode: string = "release"           # Build mode: debug, release, optimized
    --strip-symbols                             # Strip debug symbols from release builds
    --upx-compress                              # Compress binaries with UPX
    --create-containers                         # Create container images
    --generate-services                         # Generate service definitions
    --sign-binaries                             # Sign binaries (requires signing keys)
    --parallel-builds                           # Build platforms in parallel
    --verbose                                   # Enable verbose logging
] {

    let repo_root = if $source_root == "" {
        ($env.PWD | path dirname | path dirname | path dirname)
    } else {
        ($source_root | path expand)
    }

    let target_platforms_list = ($target_platforms | split row "," | each { str trim })

    let platform_config = {
        source_root: $repo_root
        output_dir: ($output_dir | path expand)
        target_platforms: $target_platforms_list
        build_mode: $build_mode
        strip_symbols: $strip_symbols
        upx_compress: $upx_compress
        create_containers: $create_containers
        generate_services: $generate_services
        sign_binaries: $sign_binaries
        parallel_builds: $parallel_builds
        verbose: $verbose
    }

    log info $"Starting platform distribution preparation with config: ($platform_config)"

    # Ensure output directory exists
    mkdir ($platform_config.output_dir)

    let preparation_results = []

    try {
        # Phase 1: Discover platform components
        let discovery_result = discover_platform_components $platform_config

        let preparation_results = ($preparation_results | append { phase: "discovery", result: $discovery_result })

        if $discovery_result.status != "success" {
            log error $"Platform component discovery failed: ($discovery_result.reason)"
            exit 1
        }

        # Phase 2: Build platform binaries
        let build_result = build_platform_binaries $platform_config $discovery_result

        let preparation_results = ($preparation_results | append { phase: "build", result: $build_result })

        if $build_result.status != "success" and $build_result.status != "partial" {
            log error $"Platform binary build failed: ($build_result.reason)"
            exit 1
        }

        # Phase 3: Post-process binaries
        let processing_result = post_process_binaries $platform_config $build_result

        let preparation_results = ($preparation_results | append { phase: "processing", result: $processing_result })

        # Phase 4: Generate service definitions
        let services_result = if $platform_config.generate_services {
            generate_service_definitions $platform_config $build_result
        } else {
            { status: "skipped", reason: "service generation disabled" }
        }

        let preparation_results = ($preparation_results | append { phase: "services", result: $services_result })

        # Phase 5: Create container images
        let containers_result = if $platform_config.create_containers {
            create_container_images $platform_config $build_result
        } else {
            { status: "skipped", reason: "container creation disabled" }
        }

        let preparation_results = ($preparation_results | append { phase: "containers", result: $containers_result })

        # Phase 6: Generate platform metadata
        let metadata_result = generate_platform_metadata $platform_config $preparation_results

        let preparation_results = ($preparation_results | append { phase: "metadata", result: $metadata_result })

        let summary = {
            source_root: $platform_config.source_root
            output_directory: $platform_config.output_dir
            target_platforms: ($platform_config.target_platforms | length)
            successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length)
            total_phases: ($preparation_results | length)
            binaries_built: ($build_result.successful_builds)
            total_size: (get_directory_size $platform_config.output_dir)
            platform_config: $platform_config
            phases: $preparation_results
        }

        log info $"Platform distribution preparation completed - ($summary.binaries_built) binaries built for ($summary.target_platforms) platforms"

        return $summary

    } catch {|err|
        log error $"Platform distribution preparation failed: ($err.msg)"
        exit 1
    }
}

# Discover platform components in the source tree
def discover_platform_components [platform_config: record]
{
    log info "Discovering platform components..."

    let start_time = (date now)

    try {
        # Define platform project locations
        let rust_projects = [
            {
                name: "orchestrator"
                path: ($platform_config.source_root | path join "orchestrator")
                binary: "provisioning-orchestrator"
                description: "Main orchestration service"
                required: true
            },
            {
                name: "control-center"
                path: ($platform_config.source_root | path join "control-center")
                binary: "control-center"
                description: "Control center API service"
                required: true
            },
            {
                name: "control-center-ui"
                path: ($platform_config.source_root | path join "control-center-ui")
                binary: "control-center-ui"
                description: "Control center web UI"
                required: false
            },
            {
                name: "mcp-server"
                path: ($platform_config.source_root | path join "provisioning" "mcp-server-rust")
                binary: "mcp-server-rust"
                description: "MCP integration server"
                required: false
            }
        ]

        # Validate project existence and Cargo.toml files
        let validated_projects = []
        let missing_projects = []

        for project in $rust_projects {
            let cargo_file = ($project.path | path join "Cargo.toml")

            if ($project.path | path exists) and ($cargo_file | path exists) {
                # Parse Cargo.toml to get project information
                let cargo_data = (open $cargo_file)
                let project_info = ($project | insert version $cargo_data.package.version | insert cargo_data $cargo_data)
                $validated_projects = ($validated_projects | append $project_info)
            } else {
                if $project.required {
                    $missing_projects = ($missing_projects | append $project.name)
                }
            }
        }

        if ($missing_projects | length) > 0 {
            return {
                status: "failed"
                reason: $"Missing required projects: ($missing_projects | str join ', ')"
                duration: ((date now) - $start_time)
            }
        }

        # Check build environment
        let build_env = validate_build_environment $platform_config

        {
            status: "success"
            rust_projects: $validated_projects
            missing_projects: $missing_projects
            total_projects: ($validated_projects | length)
            build_environment: $build_env
            duration: ((date now) - $start_time)
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Validate build environment
def validate_build_environment [platform_config: record]
{
    let build_tools = {}

    # Check Rust toolchain
    let rust_version = try {
        rustc --version | str trim
    } catch {
        "not available"
    }

    let cargo_version = try {
        cargo --version | str trim
    } catch {
        "not available"
    }

    $build_tools = ($build_tools | insert rust $rust_version | insert cargo $cargo_version)

    # Check for target platforms
    let target_availability = {}

    for platform in $platform_config.target_platforms {
        let target_triple = get_rust_target_triple $platform

        let target_installed = try {
            rustup target list --installed | lines | any {|line| $line | str contains $target_triple}
        } catch {
            false
        }

        $target_availability = ($target_availability | insert $platform $target_installed)
    }

    # Check optional tools
    let upx_available = try {
        upx --version | complete | get exit_code | $in == 0
    } catch {
        false
    }

    let strip_available = try {
        strip --version | complete | get exit_code | $in == 0
    } catch {
        false
    }

    let docker_available = try {
        docker --version | complete | get exit_code | $in == 0
    } catch {
        false
    }

    {
        build_tools: $build_tools
        target_availability: $target_availability
        optional_tools: {
            upx: $upx_available
            strip: $strip_available
            docker: $docker_available
        }
        rust_ready: ($rust_version != "not available" and $cargo_version != "not available")
    }
}

# Build platform binaries for all target platforms
def build_platform_binaries [
    platform_config: record
    discovery_result: record
]
{
    log info "Building platform binaries..."

    let start_time = (date now)

    try {
        # Build binaries for each platform
        let build_results = if $platform_config.parallel_builds {
            build_platforms_parallel $platform_config $discovery_result
        } else {
            build_platforms_sequential $platform_config $discovery_result
        }

        let successful_builds = ($build_results | where status == "success" | length)
        let total_builds = ($build_results | length)
        let failed_builds = ($build_results | where status == "failed")

        let status = if $successful_builds == 0 {
            "failed"
        } else if $successful_builds < $total_builds {
            "partial"
        } else {
            "success"
        }

        {
            status: $status
            successful_builds: $successful_builds
            total_builds: $total_builds
            failed_builds: ($failed_builds | length)
            build_results: $build_results
            duration: ((date now) - $start_time)
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Build platforms in parallel (simplified sequential for now)
def build_platforms_parallel [
    platform_config: record
    discovery_result: record
]
{
    build_platforms_sequential $platform_config $discovery_result
}

# Build platforms sequentially
def build_platforms_sequential [
    platform_config: record
    discovery_result: record
]
{
    let all_build_results = []

    for platform in $platform_config.target_platforms {
        for project in $discovery_result.rust_projects {
            let build_result = build_single_binary $platform $project $platform_config

            $all_build_results = ($all_build_results | append $build_result)
        }
    }

    return $all_build_results
}

# Build a single binary for a specific platform
def build_single_binary [
    platform: string
    project: record
    platform_config: record
]
{
    log info $"Building ($project.name) for ($platform)..."

    let start_time = (date now)
    let target_triple = get_rust_target_triple $platform

    try {
        cd $project.path

        # Build cargo command
        let cargo_cmd = ["cargo", "build"]

        # Add build mode
        if $platform_config.build_mode == "release" or $platform_config.build_mode == "optimized" {
            $cargo_cmd = ($cargo_cmd | append "--release")
        }

        # Add target platform
        $cargo_cmd = ($cargo_cmd | append ["--target", $target_triple])

        # Add optimization flags for optimized builds
        if $platform_config.build_mode == "optimized" {
            $env.RUSTFLAGS = "-C target-cpu=native -C opt-level=3 -C lto=fat"
        }

        # Execute build
        if $platform_config.verbose {
            log info $"Running: ($cargo_cmd | str join ' ')"
        }

        let build_result = (run-external --redirect-combine $cargo_cmd.0 ...$cargo_cmd.1.. | complete)

        if $build_result.exit_code == 0 {
            # Determine binary path
            let profile = if $platform_config.build_mode == "debug" { "debug" } else { "release" }
            let binary_path = ($project.path | path join "target" $target_triple $profile $project.binary)

            # Add .exe extension for Windows
            let final_binary_path = if ($platform | str contains "windows") {
                $binary_path + ".exe"
            } else {
                $binary_path
            }

            if ($final_binary_path | path exists) {
                # Copy to output directory with platform suffix
                let output_name = $"($project.binary)-($platform)"
                let output_path = ($platform_config.output_dir | path join $output_name)

                cp $final_binary_path $output_path

                let binary_size = (ls $output_path | get 0.size)

                {
                    project: $project.name
                    platform: $platform
                    target: $target_triple
                    status: "success"
                    binary_path: $output_path
                    binary_size: $binary_size
                    duration: ((date now) - $start_time)
                }
            } else {
                {
                    project: $project.name
                    platform: $platform
                    target: $target_triple
                    status: "failed"
                    reason: $"binary not found at ($final_binary_path)"
                    duration: ((date now) - $start_time)
                }
            }
        } else {
            {
                project: $project.name
                platform: $platform
                target: $target_triple
                status: "failed"
                reason: $build_result.stderr
                duration: ((date now) - $start_time)
            }
        }

    } catch {|err|
        {
            project: $project.name
            platform: $platform
            target: $target_triple
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Get Rust target triple for platform
def get_rust_target_triple [platform: string]
{
    match $platform {
        "linux-amd64" => "x86_64-unknown-linux-gnu"
        "linux-arm64" => "aarch64-unknown-linux-gnu"
        "macos-amd64" => "x86_64-apple-darwin"
        "macos-arm64" => "aarch64-apple-darwin"
        "windows-amd64" => "x86_64-pc-windows-gnu"
        "windows-arm64" => "aarch64-pc-windows-msvc"
        _ => $platform  # Assume it's already a target triple
    }
}

# Post-process binaries (strip, compress, sign)
def post_process_binaries [
    platform_config: record
    build_result: record
]
{
    log info "Post-processing binaries..."

    let start_time = (date now)

    try {
        let successful_builds = ($build_result.build_results | where status == "success")
        let processing_results = []

        for build in $successful_builds {
            let processing_result = process_single_binary $build $platform_config

            $processing_results = ($processing_results | append $processing_result)
        }

        let successful_processing = ($processing_results | where status == "success" | length)
        let total_processing = ($processing_results | length)

        {
            status: (if $successful_processing == $total_processing { "success" } else { "partial" })
            processed_binaries: $successful_processing
            total_binaries: $total_processing
            processing_results: $processing_results
            duration: ((date now) - $start_time)
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Process a single binary
def process_single_binary [
    build: record
    platform_config: record
]
{
    let processing_steps = []
    let binary_path = $build.binary_path

    try {
        let original_size = $build.binary_size

        # Strip debug symbols if requested
        if $platform_config.strip_symbols and not ($build.platform | str contains "windows") {
            let strip_result = strip_binary_symbols $binary_path $platform_config

            $processing_steps = ($processing_steps | append { step: "strip", result: $strip_result })
        }

        # UPX compress if requested
        let final_size = $original_size

        if $platform_config.upx_compress {
            let upx_result = upx_compress_binary $binary_path $platform_config

            $processing_steps = ($processing_steps | append { step: "upx", result: $upx_result })

            if $upx_result.status == "success" {
                $final_size = $upx_result.compressed_size
            }
        }

        # Sign binary if requested
        if $platform_config.sign_binaries {
            let sign_result = sign_binary $binary_path $platform_config

            $processing_steps = ($processing_steps | append { step: "sign", result: $sign_result })
        }

        # Update final size
        let current_size = (ls $binary_path | get 0.size)
        let compression_ratio = (($current_size | into float) / ($original_size | into float) * 100)

        {
            project: $build.project
            platform: $build.platform
            binary_path: $binary_path
            status: "success"
            original_size: $original_size
            final_size: $current_size
            compression_ratio: $compression_ratio
            processing_steps: $processing_steps
        }

    } catch {|err|
        {
            project: $build.project
            platform: $build.platform
            binary_path: $binary_path
            status: "failed"
            reason: $err.msg
            processing_steps: $processing_steps
        }
    }
}

# Strip debug symbols from binary
def strip_binary_symbols [binary_path: string, platform_config: record]
{
    try {
        if $platform_config.verbose {
            log info $"Stripping symbols from: ($binary_path)"
        }

        let strip_result = (strip $binary_path | complete)

        if $strip_result.exit_code == 0 {
            { status: "success", stripped: true }
        } else {
            { status: "failed", reason: $strip_result.stderr }
        }

    } catch {|err|
        { status: "failed", reason: $err.msg }
    }
}

# Compress binary with UPX
def upx_compress_binary [binary_path: string, platform_config: record]
{
    try {
        if $platform_config.verbose {
            log info $"UPX compressing: ($binary_path)"
        }

        let original_size = (ls $binary_path | get 0.size)

        let upx_result = (upx --best $binary_path | complete)

        if $upx_result.exit_code == 0 {
            let compressed_size = (ls $binary_path | get 0.size)
            let ratio = (($compressed_size | into float) / ($original_size | into float) * 100)

            {
                status: "success"
                compressed: true
                original_size: $original_size
                compressed_size: $compressed_size
                compression_ratio: $ratio
            }
        } else {
            { status: "failed", reason: $upx_result.stderr }
        }

    } catch {|err|
        { status: "failed", reason: $err.msg }
    }
}

# Sign binary (placeholder - would need actual signing implementation)
def sign_binary [binary_path: string, platform_config: record]
{
    log warning "Binary signing not implemented - skipping"
    { status: "skipped", reason: "signing not implemented" }
}

# Generate service definitions
def generate_service_definitions [
    platform_config: record
    build_result: record
]
{
    log info "Generating service definitions..."

    let start_time = (date now)

    try {
        let services_dir = ($platform_config.output_dir | path join "services")
        mkdir $services_dir

        let successful_builds = ($build_result.build_results | where status == "success")
        let generated_services = []

        # Generate systemd service files
        for build in $successful_builds {
            if $build.platform | str starts-with "linux" {
                let systemd_service = generate_systemd_service $build $platform_config

                let service_file = ($services_dir | path join $"($build.project).service")
                $systemd_service | save $service_file

                $generated_services = ($generated_services | append {
                    project: $build.project
                    platform: $build.platform
                    service_type: "systemd"
                    service_file: $service_file
                })
            }
        }

        # Generate Docker Compose definitions
        let docker_compose = generate_docker_compose $successful_builds $platform_config

        let compose_file = ($services_dir | path join "docker-compose.yml")
        $docker_compose | save $compose_file

        {
            status: "success"
            services_generated: ($generated_services | length)
            services_directory: $services_dir
            generated_services: $generated_services
            docker_compose: $compose_file
            duration: ((date now) - $start_time)
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Generate systemd service file
def generate_systemd_service [build: record, platform_config: record]
{
    $"[Unit]
Description=Provisioning ($build.project | str title-case) Service
After=network.target
Wants=network.target

[Service]
Type=simple
User=provisioning
Group=provisioning
ExecStart=/usr/local/bin/($build.project | str replace "_" "-")
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=provisioning-($build.project)

# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/provisioning /var/log/provisioning

[Install]
WantedBy=multi-user.target
"
}

# Generate Docker Compose file
def generate_docker_compose [builds: list, platform_config: record]
{
    let services = []

    for build in $builds {
        let service_def = $"  ($build.project | str replace "_" "-"):
    image: provisioning/($build.project):latest
    restart: unless-stopped
    ports:
      - \"8080:8080\"  # Adjust port as needed
    volumes:
      - provisioning-data:/data
      - ./config:/etc/provisioning:ro
    environment:
      - PROVISIONING_ENV=production
    healthcheck:
      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8080/health\"]
      interval: 30s
      timeout: 10s
      retries: 3"

        $services = ($services | append $service_def)
    }

    let compose_content = $"version: '3.8'

services:
($services | str join "\n\n")

volumes:
  provisioning-data:
    driver: local

networks:
  default:
    name: provisioning
"

    return $compose_content
}

# Create container images
def create_container_images [
    platform_config: record
    build_result: record
]
{
    log info "Creating container images..."

    let start_time = (date now)

    # Container creation would use the build-containers.nu tool
    let containers_result = try {
        nu ($platform_config.source_root | path join "src" "tools" "package" "build-containers.nu")
            --dist-dir ($platform_config.output_dir | path dirname)
            --tag-prefix "provisioning"
            --platforms "linux/amd64"
            --verbose:$platform_config.verbose

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
        }
    }

    {
        status: $containers_result.status
        containers_created: (if "successful_builds" in ($containers_result | columns) { $containers_result.successful_builds } else { 0 })
        container_results: $containers_result
        duration: ((date now) - $start_time)
    }
}

# Generate platform metadata
def generate_platform_metadata [
    platform_config: record
    preparation_results: list
]
{
    log info "Generating platform metadata..."

    let start_time = (date now)

    try {
        let metadata = {
            name: "provisioning-platform"
            version: (detect_version $platform_config.source_root)
            type: "platform-distribution"
            created_at: (date now)
            created_by: "platform-distribution-tool"
            build_configuration: $platform_config
            target_platforms: $platform_config.target_platforms
            preparation_phases: ($preparation_results | each {|r|
                {
                    phase: $r.phase
                    status: $r.result.status
                    duration: (if "duration" in ($r.result | columns) { $r.result.duration } else { 0 })
                }
            })
            build_statistics: {
                total_phases: ($preparation_results | length)
                successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length)
                distribution_size: (get_directory_size $platform_config.output_dir)
            }
        }

        let metadata_file = ($platform_config.output_dir | path join "platform-metadata.json")
        $metadata | to json | save $metadata_file

        {
            status: "success"
            metadata_file: $metadata_file
            metadata: $metadata
            duration: ((date now) - $start_time)
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Detect version from git or other sources
def detect_version [repo_root: string]
{
    cd $repo_root

    try {
        let git_version = (git describe --tags --always --dirty 2>/dev/null | str trim)
        if $git_version != "" {
            return ($git_version | str replace "^v" "")
        }

        return $"dev-(date now | format date '%Y%m%d')"
    } catch {
        return "unknown"
    }
}

# Get directory size helper
def get_directory_size [dir: string] -> int {
    if not ($dir | path exists) {
        return 0
    }

    try {
        find $dir -type f | each {|file| ls $file | get 0.size } | math sum | if $in == null { 0 } else { $in }
    } catch {
        0
    }
}

# Show platform distribution status
def "main status" [] {
    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)
    let version = (detect_version $repo_root)

    # Check for platform components
    let orchestrator_exists = (($repo_root | path join "orchestrator" "Cargo.toml") | path exists)
    let control_center_exists = (($repo_root | path join "control-center" "Cargo.toml") | path exists)

    let build_env = validate_build_environment {
        target_platforms: ["linux-amd64", "macos-amd64", "windows-amd64"]
    }

    {
        repository: $repo_root
        version: $version
        platform_components: {
            orchestrator: $orchestrator_exists
            control_center: $control_center_exists
        }
        build_environment: $build_env
        ready_for_build: ($build_env.rust_ready and $orchestrator_exists and $control_center_exists)
        supported_platforms: ["linux-amd64", "linux-arm64", "macos-amd64", "macos-arm64", "windows-amd64"]
    }
}

# Quick platform build for single target
def "main quick" [
    --platform: string = "linux-amd64"         # Single platform to build
    --output-dir: string = "dist/platform"     # Output directory
] {
    main --target-platforms $platform --output-dir $output_dir --parallel-builds:false
}
