"""
Data upload commands using S3-based multi-org approach.
"""

import sys
from pathlib import Path
from typing import Optional

import typer
from rich.console import Console

from src.upload.models import UploadConfig, OrganizationResult
from src.upload.services import UploadServiceFactory
from src.services.dataset_identification import UploadTrackingService
from src.services.s3_data_service import S3DataService

console = Console()

def upload_generated(
    config: str = typer.Option(..., "--config", help="Upload configuration file (YAML)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Dry run mode - validate config and show what would be uploaded"),
    recreate_connections: bool = typer.Option(False, "--recreate-connections", help="Delete and recreate CloudZero connections for optimized first ingest"),
    delete_data: bool = typer.Option(False, "--delete-data", help="Delete S3 data when recreating connections (requires --recreate-connections)"),
    append: bool = typer.Option(False, "--append", help="Append new data to existing drops instead of creating new drops"),
    verbose: bool = typer.Option(False, "--verbose", help="Verbose output with detailed progress"),
    api: bool = typer.Option(False, "--api", help="Execute via API - same as default (kept for backward compatibility)"),
    local: bool = typer.Option(False, "--local", help="Execute locally instead of via API (for development/debugging)"),
) -> None:
    """
    Upload generated datasets to CloudZero S3 for multiple organizations.

    Loads a generated dataset from S3 storage and uploads it to CloudZero S3 buckets
    for multiple organizations with proper CBF formatting, drop ID management,
    and manifest handling.

    Execution modes:
    - Default: Submit to Lambda API for processing on ECS workers (scalable, observable)
    - --local: Execute locally for development, debugging, or small configs
    - --api: Explicit API execution (same as default, kept for backward compatibility)

    Example configuration file:

    upload_config:
      dataset_name: "2025-01-15_14-30-22"
      months: ["2024-07", "2024-08"]
      organizations:
        - name: "TradeShow"
          api_token_env: "CLOUDZERO_TRADESHOW_API_TOKEN"
          providers:
            AWS:
              months: ["2024-07", "2024-08"]
            GCP: null
          connections:
            AWS: ["tradeshow-aws-connection-1"]
            GCP: ["tradeshow-gcp-connection-1"]
    """
    # Validate mutually exclusive flags
    if api and local:
        console.print("[red]❌ Cannot specify both --api and --local[/red]")
        raise typer.Exit(1)

    # Execute locally if --local flag provided
    if local:
        console.print("[dim]⚙️  Executing locally...[/dim]")
        _execute_upload_locally(
            config, dry_run, recreate_connections,
            delete_data, append, verbose
        )
        return

    # Default: Execute via API
    console.print("[cyan]☁️  Executing via API (Lambda + ECS workers)...[/cyan]")
    from pathlib import Path
    from src.api.cli_helper import execute_stage_via_api

    execute_stage_via_api(
        job_type="upload",
        config_path=Path(config),
        dry_run=dry_run,
        recreate_connections=recreate_connections,
        delete_data=delete_data,
        append=append,
    )


def _execute_upload_locally(
    config: str,
    dry_run: bool,
    recreate_connections: bool,
    delete_data: bool,
    append: bool,
    verbose: bool
) -> None:
    """Execute upload locally."""

    try:
        # FAIL FAST: Validate AWS credentials before any work
        from src.services.environment_service import EnvironmentService
        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()

    except ValueError as e:
        console.print(f"❌ {str(e)}")
        raise typer.Exit(1)

    # Validate flag combinations
    if delete_data and not recreate_connections:
        console.print("❌ --delete-data requires --recreate-connections", style="red")
        raise typer.Exit(1)

    try:
        # Load and validate configuration
        config_path = Path(config)
        if not config_path.exists():
            console.print(f"❌ Configuration file not found: {config}", style="red")
            raise typer.Exit(1)
        
        console.print(f"📋 Loading upload configuration: {config_path}")
        upload_config = UploadConfig.from_yaml(config_path)
        
        # Apply CLI append flag (overrides config file setting)
        if append:
            upload_config.append_mode = True
            console.print("🔄 Append mode enabled via CLI flag")

        # Display configuration summary
        if dry_run:
            mode_text = "DRY RUN"
        elif recreate_connections and delete_data:
            mode_text = "RECREATE & DELETE"
        elif recreate_connections:
            mode_text = "RECREATE (PRESERVE DATA)"
        elif upload_config.append_mode:
            mode_text = "APPEND"
        else:
            mode_text = "UPLOAD"

        console.print(f"🚀 {mode_text} Configuration:")
        console.print(f"   Dataset: {upload_config.dataset_name}")
        console.print(f"   Stage Discovery: Automatic (discount > usage > metadata > k8s > generate)")
        console.print(f"   Organizations: {len(upload_config.organizations)}")

        if recreate_connections and delete_data:
            console.print("🔄 Mode 3: Recreate connections + delete data - full recreation")
        elif recreate_connections:
            console.print("🔄 Mode 2: Recreate connections, preserve data - optimizes first ingest")
        
        if verbose:
            if upload_config.months:
                console.print(f"   Global months: {upload_config.months}")
            if upload_config.providers:
                console.print(f"   Global providers: {upload_config.providers}")
            for org in upload_config.organizations:
                console.print(f"     • {org.name} - Providers: {org.providers}")
                if org.months:
                    console.print(f"       Months override: {org.months}")
                if org.stage_overrides:
                    console.print(f"       Stage overrides: {org.stage_overrides}")
        
        if dry_run:
            if recreate_connections:
                console.print("🧪 DRY RUN MODE - Showing connection recreation impact")
            else:
                console.print("🧪 DRY RUN MODE - Validation only, no files will be uploaded")
        
        # Create coordinator using new refactored services
        coordinator = UploadServiceFactory.create_coordinator(upload_config)
        # Handle recreate connections workflow
        if recreate_connections:
            if dry_run:
                console.print(f"\n🔍 Analyzing connection recreation impact...")
                result = coordinator.show_recreate_connections_plan(upload_config)
            else:
                # Confirm destructive operation with mode-specific warnings
                if delete_data:
                    console.print("\n⚠️  Mode 3: Full Recreation Impact:")
                    console.print("   • Will delete existing CloudZero connections")
                    console.print("   • Will delete ALL S3 data for affected providers")
                    console.print("   • Will reprocess and upload fresh data")
                    console.print("   • Source datasets in czbillgen/ will be preserved")
                    console.print("   • Connections will be recreated for optimized first ingest")
                else:
                    console.print("\n⚠️  Mode 2: Connection Recreation Impact:")
                    console.print("   • Will delete existing CloudZero connections")
                    console.print("   • Will PRESERVE existing S3 data (skip reprocessing if data exists)")
                    console.print("   • Will only process providers without existing data")
                    console.print("   • Source datasets in czbillgen/ will be preserved")
                    console.print("   • Connections will be recreated for optimized first ingest")

                if not typer.confirm("\nContinue with connection recreation?"):
                    console.print("❌ Operation cancelled by user")
                    raise typer.Exit(0)

                console.print(f"\n🔄 Starting connection recreation and upload...")
                result = coordinator.recreate_connections_and_upload(upload_config, delete_data=delete_data)
        else:
            # Normal upload workflow
            if dry_run:
                console.print(f"\n🧪 Starting validation process...")
            else:
                console.print(f"\n🚀 Starting upload process...")
            result = coordinator.upload_dataset(upload_config, dry_run=dry_run)
        
        # Display results
        mode_text = "Validation" if dry_run else "Upload"
        console.print(f"\n🎯 {mode_text} Results:")
        console.print(f"   Status: {result.status}")
        console.print(f"   Dataset: {result.dataset}")
        console.print(f"   Organizations: {result.organizations_successful}/{result.organizations_processed} successful")
        
        if verbose or result.status != 'success':
            console.print(f"\n📊 Detailed Results:")
            
            # Handle different result structures based on mode
            if result.mode == 'recreate_connections_dry_run':
                # Dry run mode - results contain plan info, not org results
                console.print("   🧪 Dry run mode - no actual operations performed")
            elif result.results and isinstance(result.results, dict):
                # Normal upload mode - results contain org-level results
                for org_name, org_result in result.results.items():
                    if isinstance(org_result, OrganizationResult):
                        status_icon = "✅" if org_result.status == "success" else "❌"
                        console.print(f"   {status_icon} {org_name}: {org_result.status}")

                        if org_result.status == "success":
                            console.print(f"      Files: {org_result.files_uploaded}")
                            console.print(f"      Rows: {org_result.total_rows:,}")
                            console.print(f"      Drop ID: {org_result.drop_id or 'N/A'}")
                        else:
                            console.print(f"      Error: {org_result.error_message or 'Unknown error'}")
                    elif isinstance(org_result, dict) and "status" in org_result:
                        # Legacy dict format support
                        status_icon = "✅" if org_result["status"] == "success" else "❌"
                        console.print(f"   {status_icon} {org_name}: {org_result['status']}")

                        if org_result["status"] == "success":
                            console.print(f"      Files: {org_result.get('files_uploaded', 0)}")
                            console.print(f"      Rows: {org_result.get('total_rows', 0):,}")
                            console.print(f"      Drop ID: {org_result.get('drop_id', 'N/A')}")
                        else:
                            console.print(f"      Error: {org_result.get('error', 'Unknown error')}")
                    else:
                        console.print(f"   📋 {org_name}: {org_result}")
            else:
                console.print("   No detailed results available")
        
        # Exit with appropriate code
        if result.status == 'success':
            if dry_run:
                console.print(f"\n✅ Validation completed successfully!")
            else:
                console.print(f"\n✅ Upload completed successfully!")

                # Record upload tracking events for successful uploads
                if not dry_run:
                    try:
                        s3_service = S3DataService()
                        tracking_service = UploadTrackingService(s3_service)
                        _record_successful_upload_events(tracking_service, upload_config, result, append)
                    except Exception:
                        # Upload tracking failure shouldn't break the upload success
                        pass

            sys.exit(0)
        elif result.status == 'partial':
            if dry_run:
                console.print(f"\n⚠️  Validation completed with some failures!")
            else:
                console.print(f"\n⚠️  Upload completed with some failures!")
            sys.exit(1)
        else:
            if dry_run:
                console.print(f"\n❌ Validation failed!")
            else:
                console.print(f"\n❌ Upload failed!")
            sys.exit(1)
        
    except Exception as e:
        console.print(f"❌ Upload failed: {str(e)}", style="red")
        if verbose:
            import traceback
            console.print(f"Traceback: {traceback.format_exc()}", style="red")
        raise typer.Exit(1)


def _validate_aws_credentials() -> None:
    """Validate AWS credentials before any work."""
    try:
        from src.services.environment_service import EnvironmentService
        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()
    except ValueError as e:
        console.print(f"❌ {str(e)}")
        raise typer.Exit(1)


def _load_and_validate_adhoc_config(config_path_str: str) -> UploadConfig:
    """Load and validate ad-hoc upload configuration."""
    try:
        config_path = Path(config_path_str)
        if not config_path.exists():
            console.print(f"❌ Configuration file not found: {config_path_str}", style="red")
            raise typer.Exit(1)

        console.print(f"📋 Loading upload configuration: {config_path}")
        upload_config = UploadConfig.from_yaml(config_path)

        if not upload_config.is_adhoc_upload():
            console.print("❌ Configuration file does not contain ad-hoc upload settings", style="red")
            console.print("Expected 'adhoc' section with 'csv_path' in upload_config", style="red")
            raise typer.Exit(1)

        # Validate that all organizations have explicit connections for their providers
        _validate_adhoc_connections(upload_config)

        return upload_config

    except Exception as e:
        console.print(f"❌ Failed to load configuration: {str(e)}", style="red")
        raise typer.Exit(1)


def _validate_adhoc_connections(upload_config: UploadConfig) -> None:
    """
    Validate that all organizations have explicit connection IDs configured.

    Ad-hoc uploads REQUIRE explicit connections because:
    - External data with unknown S3 structure
    - Connections must be configured manually in CloudZero UI first
    """
    for org in upload_config.organizations:
        if not org.connections:
            console.print(f"\n❌ Organization '{org.name}' missing required connections", style="red")
            console.print(f"\n💡 Ad-hoc uploads require explicit connection IDs.", style="yellow")
            console.print(f"\nTo fix this:", style="yellow")
            console.print(f"1. Create billing connections in CloudZero UI (Settings > Connections)")
            console.print(f"2. Add connection IDs to your config:\n")
            console.print(f"   organizations:", style="cyan")
            console.print(f"     {org.name}:", style="cyan")
            console.print(f"       connections:", style="cyan")
            for provider in org.providers:
                console.print(f"         {provider}:", style="cyan")
                console.print(f"           - \"your-connection-id-here\"", style="cyan")
            console.print(f"\nFind connection IDs in CloudZero UI under Settings > Connections")
            raise typer.Exit(1)

        # Check each provider has connections
        missing_providers = []
        for provider in org.providers:
            if provider not in org.connections or not org.connections[provider]:
                missing_providers.append(provider)

        if missing_providers:
            console.print(f"\n❌ Organization '{org.name}' missing connections for: {', '.join(missing_providers)}", style="red")
            console.print(f"\n💡 Ad-hoc uploads require explicit connection IDs for all providers.", style="yellow")
            console.print(f"\nAdd to your config:\n")
            console.print(f"   organizations:", style="cyan")
            console.print(f"     {org.name}:", style="cyan")
            console.print(f"       connections:", style="cyan")
            for provider in missing_providers:
                console.print(f"         {provider}:", style="cyan")
                console.print(f"           - \"your-connection-id-here\"", style="cyan")
            raise typer.Exit(1)


def _validate_adhoc_method(method: str, upload_config: UploadConfig) -> None:
    """Validate upload method selection."""
    if method not in ["api", "s3"]:
        console.print(f"❌ Invalid method: {method}. Must be 'api' or 's3'", style="red")
        raise typer.Exit(1)

    # Validate CSV size for API method
    if method == "api":
        csv_path = Path(upload_config.adhoc.csv_path)
        csv_size_mb = csv_path.stat().st_size / (1024**2)

        if csv_size_mb > 500:
            console.print(
                f"❌ CSV too large for API method: {csv_size_mb:.1f}MB (max 500MB)\n"
                f"   Use --method s3 for large datasets",
                style="red"
            )
            raise typer.Exit(1)

        if csv_size_mb > 100:
            console.print(
                f"⚠️  Large CSV detected: {csv_size_mb:.1f}MB\n"
                f"   Consider using --method s3 for better performance",
                style="yellow"
            )


def _display_adhoc_config_summary(upload_config: UploadConfig, verbose: bool, dry_run: bool, method: str) -> None:
    """Display ad-hoc upload configuration summary."""
    method_desc = "API (direct)" if method == "api" else "S3 (bucket)"
    console.print(f"🚀 Ad-hoc Upload Configuration:")
    console.print(f"   Method: {method_desc}")
    console.print(f"   CSV file: {upload_config.adhoc.csv_path}")

    if upload_config.adhoc.mapping_path:
        console.print(f"   Mapping file: {upload_config.adhoc.mapping_path}")
    else:
        console.print(f"   Mapping: Default CBF mapping")

    console.print(f"   Organizations: {len(upload_config.organizations)}")

    if verbose:
        for org in upload_config.organizations:
            console.print(f"     • {org.name} - Providers: {org.providers}")

    if dry_run:
        console.print("🧪 DRY RUN MODE - Configuration validated, no files will be uploaded")


def _execute_adhoc_upload(upload_config: UploadConfig, dry_run: bool, method: str):
    """Execute ad-hoc upload workflow."""
    try:
        if method == "api":
            # Use AnyCost API upload
            from src.upload.api import AnyCostUploader
            from src.upload.services.data_processing_service import DataProcessingService
            from src.upload.models import CBFProcessingConfig

            # Create CBF processing config
            cbf_config = CBFProcessingConfig(mapping_path=upload_config.cbf_mapping_path)
            data_processor = DataProcessingService(cbf_config)
            uploader = AnyCostUploader(data_processor)

            console.print(f"\n🚀 Starting ad-hoc upload (API method)...")
            return uploader.upload_adhoc_csv(upload_config, dry_run=dry_run)
        else:
            # Use S3 upload (existing)
            coordinator = UploadServiceFactory.create_coordinator(upload_config)
            console.print(f"\n🚀 Starting ad-hoc upload (S3 method)...")
            return coordinator.upload_adhoc_csv(upload_config, dry_run=dry_run)
    except Exception as e:
        console.print(f"❌ Ad-hoc upload failed: {str(e)}", style="red")
        raise typer.Exit(1)


def _display_adhoc_results(result, upload_config: UploadConfig, verbose: bool) -> None:
    """Display ad-hoc upload results."""
    console.print(f"\n🎯 Upload Results:")
    console.print(f"   Status: {result.status}")
    console.print(f"   CSV file: {upload_config.adhoc.csv_path}")
    console.print(f"   Organizations: {result.organizations_successful}/{result.organizations_processed} successful")

    if verbose or result.status != 'success':
        console.print(f"\n📊 Detailed Results:")
        for org_name, org_result in result.results.items():
            status_icon = "✅" if org_result.status == "success" else "❌"
            console.print(f"   {status_icon} {org_name}: {org_result.status}")

            if org_result.status == "success":
                console.print(f"      Files: {org_result.files_uploaded}")
                console.print(f"      Rows: {org_result.total_rows:,}")
                console.print(f"      Providers: {org_result.providers}")
            else:
                console.print(f"      Error: {org_result.error_message or 'Unknown error'}")


def _exit_with_adhoc_status(result) -> None:
    """Exit with appropriate status code based on result."""
    if result.status == 'success':
        console.print(f"\n✅ Ad-hoc upload completed successfully!")
        sys.exit(0)
    elif result.status == 'partial':
        console.print(f"\n⚠️  Ad-hoc upload completed with some failures!")
        sys.exit(1)
    else:
        console.print(f"\n❌ Ad-hoc upload failed!")
        sys.exit(1)


def upload_adhoc(
    config: str = typer.Option(..., "--config", help="Upload configuration file (YAML)"),
    method: str = typer.Option("api", "--method", help="Upload method: 'api' (default, fast) or 's3' (large datasets)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Dry run mode - validate config and show what would be uploaded"),
    verbose: bool = typer.Option(False, "--verbose", help="Verbose output with detailed progress"),
) -> None:
    """
    Upload ad-hoc CSV data to CloudZero.

    Supports two upload methods:
      - API: Direct upload via AnyCost Stream API (default)
             Best for small CSVs (<100MB), immediate ingestion
      - S3:  Upload via S3 bucket (use --method s3)
             Best for large CSVs (>100MB), handles unlimited size

    REQUIRED: Custom CBF mapping file to map your CSV columns to CloudZero format.
    See: configs/upload-adhoc/orgs/Elekta/mapping.csv for example

    Example configuration file:

    upload_config:
      adhoc:
        csv_path: "data/my-billing-data.csv"
        mapping_path: "configs/upload-adhoc/orgs/MyOrg/mapping.csv"  # REQUIRED
      organizations:
        MyOrg:
          api_token_env: "CLOUDZERO_MYORG_API_TOKEN"
          providers: ["AWS", "GCP"]

    CBF mapping file format (CSV with 2 columns):
      source_column,cbf_column
      invoice_id,lineitem/id
      usage_date,time/usage_start
      total_cost,cost/cost
      environment,resource/tag:environment

    Examples:
      # Dry-run: build and inspect payloads
      czbillgen upload adhoc --config adhoc.yaml --dry-run

      # API upload (default)
      czbillgen upload adhoc --config adhoc.yaml

      # S3 upload for large dataset
      czbillgen upload adhoc --config adhoc.yaml --method s3

    Full guide: configs/upload-adhoc/README.md
    """
    _validate_aws_credentials()
    upload_config = _load_and_validate_adhoc_config(config)
    _validate_adhoc_method(method, upload_config)
    _display_adhoc_config_summary(upload_config, verbose, dry_run, method)
    result = _execute_adhoc_upload(upload_config, dry_run, method)
    _display_adhoc_results(result, upload_config, verbose)
    _exit_with_adhoc_status(result)


def copy_provider(
    source_org: str = typer.Option(..., "--source-org", help="Source organization name"),
    target_org: str = typer.Option(..., "--target-org", help="Target organization name"),
    provider: str = typer.Option(..., "--provider", help="Provider to copy (AWS, Azure, GCP, etc)"),
    recreate_connection: bool = typer.Option(False, "--recreate-connection", help="Delete and recreate target org connection before copying"),
    delete_data: bool = typer.Option(False, "--delete-data", help="Delete S3 data when recreating connection (requires --recreate-connection)"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Show copy plan without executing"),
    create_connection: bool = typer.Option(True, "--create-connection/--no-create-connection",
                                          help="Create/validate CloudZero connection (default: true)"),
    verbose: bool = typer.Option(False, "--verbose", help="Verbose output with detailed progress"),
) -> None:
    """
    Copy provider billing data from source to target organization.

    Discovers all billing periods from source org manifest, copies files via
    S3-to-S3 operations (very fast), updates target manifest, and optionally
    creates/validates CloudZero connection.

    Example:
        czbillgen upload copy-provider --source-org DEMOSHI --target-org DEMOINSIGHT --provider Azure

    This will:
      1. Discover all Azure billing periods in DEMOSHI from manifest.json
      2. Copy all Azure files to DEMOINSIGHT (S3-to-S3, no download)
      3. Update DEMOINSIGHT manifest.json
      4. Create/validate DEMOINSIGHT Azure connection
    """
    try:
        # FAIL FAST: Validate AWS credentials before any work
        from src.services.environment_service import EnvironmentService
        env_service = EnvironmentService()
        env_service.validate_aws_credentials_and_access()

    except ValueError as e:
        console.print(f"❌ {str(e)}")
        raise typer.Exit(1)

    try:
        # Validate flag combinations
        if recreate_connection and not create_connection:
            console.print("❌ Invalid flags: Cannot use --recreate-connection with --no-create-connection", style="red")
            console.print("   Recreation implies creating a new connection after deletion", style="red")
            raise typer.Exit(1)

        if delete_data and not recreate_connection:
            console.print("❌ --delete-data requires --recreate-connection", style="red")
            raise typer.Exit(1)

        console.print(f"📋 Provider Copy Configuration:")
        console.print(f"   Source Org: {source_org}")
        console.print(f"   Target Org: {target_org}")
        console.print(f"   Provider: {provider}")
        if recreate_connection and delete_data:
            console.print(f"   Mode: Recreate + Delete Data ⚠️")
        elif recreate_connection:
            console.print(f"   Mode: Recreate (Preserve Data) ⚠️")
        else:
            console.print(f"   Create Connection: {'Yes' if create_connection else 'No'}")

        if dry_run:
            console.print("🧪 DRY RUN MODE - No files will be copied")

        # Show recreation warning if requested
        if recreate_connection and not dry_run:
            if delete_data:
                console.print(f"\n⚠️  Mode 3: Full Recreation Warning:")
                console.print(f"   • Will delete {target_org} connection for {provider}")
                console.print(f"   • Will delete ALL S3 data: {target_org}/{provider}/")
                console.print(f"   • Will copy fresh data from {source_org}")
                console.print(f"   • This is PERMANENT and cannot be undone")
                console.print(f"   • Other providers in {target_org} will NOT be affected")
            else:
                console.print(f"\n⚠️  Mode 2: Connection Recreation Warning:")
                console.print(f"   • Will delete {target_org} connection for {provider}")
                console.print(f"   • Will PRESERVE existing S3 data: {target_org}/{provider}/")
                console.print(f"   • Will copy data from {source_org} (may skip if data exists)")
                console.print(f"   • Other providers in {target_org} will NOT be affected")

            if not typer.confirm(f"\nContinue with recreation for {target_org} {provider}?"):
                console.print("❌ Operation cancelled by user")
                raise typer.Exit(0)

        # Create orchestrator
        from src.upload.services.provider_copy_orchestrator import (
            ProviderCopyOrchestratorFactory
        )
        orchestrator = ProviderCopyOrchestratorFactory.create()

        # Execute copy
        console.print(f"\n{'🧪 Analyzing' if dry_run else '🚀 Starting'} provider copy...")
        result = orchestrator.copy_provider_to_org(
            source_org=source_org,
            target_org=target_org,
            provider=provider,
            billing_periods=None,  # Copy all periods from source
            dry_run=dry_run,
            create_connection=create_connection,
            recreate_connection=recreate_connection,
            delete_data=delete_data
        )

        # Display results
        console.print(f"\n🎯 Copy Results:")
        console.print(f"   Status: {result.status}")
        console.print(f"   Billing Periods: {len(result.billing_periods_copied)}")
        console.print(f"   Files Copied: {result.files_copied}")
        if result.files_failed > 0:
            console.print(f"   Files Failed: {result.files_failed}")
        console.print(f"   Total Size: {result.total_size_bytes / (1024**3):.2f} GB")

        if verbose or result.copy_errors:
            if result.copy_errors:
                console.print(f"\n⚠️  Errors ({len(result.copy_errors)}):")
                for error in result.copy_errors[:10]:  # Show first 10
                    console.print(f"   • {error}")

            if verbose and result.billing_periods_copied:
                console.print(f"\n📅 Billing Periods Copied:")
                for period in result.billing_periods_copied:
                    console.print(f"   • {period}")

        if result.status == "success":
            console.print(f"\n✅ Provider copy completed successfully!")
            sys.exit(0)
        elif result.status == "partial":
            console.print(f"\n⚠️  Provider copy completed with some failures!")
            sys.exit(1)
        else:
            console.print(f"\n❌ Provider copy failed!")
            sys.exit(1)

    except Exception as e:
        console.print(f"❌ Copy failed: {str(e)}", style="red")
        if verbose:
            import traceback
            console.print(f"Traceback: {traceback.format_exc()}", style="red")
        raise typer.Exit(1)


def _record_successful_upload_events(tracking_service: UploadTrackingService, upload_config: UploadConfig, result, append: bool):
    """Record upload tracking events for successful uploads."""

    # Determine upload type
    upload_type = "append" if append else "new"

    # Create date range folder format from months
    if upload_config.months:
        sorted_months = sorted(upload_config.months)
        start_date = sorted_months[0] + "-01"
        end_month = sorted_months[-1]
        # Calculate last day of month (simplified)
        if end_month.endswith(('-01', '-03', '-05', '-07', '-08', '-10', '-12')):
            end_date = end_month + "-31"
        elif end_month.endswith(('-04', '-06', '-09', '-11')):
            end_date = end_month + "-30"
        else:
            end_date = end_month + "-28"  # February, simplified

        # Convert to YYYYMMDD format
        date_range_folder = start_date.replace('-', '') + '-' + end_date.replace('-', '')
    else:
        # Default if no months specified
        from datetime import datetime
        current_date = datetime.now().strftime("%Y%m%d")
        date_range_folder = f"{current_date}-{current_date}"

    # Record upload events for each organization
    for org in upload_config.organizations:
        try:
            # Determine providers and months for this org
            org_providers = list(org.providers.keys()) if org.providers else []
            org_months = org.months if org.months else (upload_config.months or [])

            if not org_providers:
                continue  # Skip if no providers

            # Get drop_id from upload result
            drop_id = "unknown"  # Default fallback
            if hasattr(result, 'results') and result.results and org.name in result.results:
                org_result = result.results[org.name]
                if hasattr(org_result, 'drop_id'):
                    drop_id = org_result.drop_id
                elif isinstance(org_result, dict):
                    drop_id = org_result.get('drop_id', 'unknown')

            # Create upload event
            upload_event = tracking_service.create_upload_event(
                dataset_id=upload_config.dataset_name,
                organization=org.name,
                upload_type=upload_type,
                providers_uploaded=org_providers,
                months_uploaded=org_months,
                date_range_folder=date_range_folder,
                drop_id=drop_id
            )

            # Record the event
            tracking_service.record_upload(upload_event)

        except Exception:
            # Continue with other organizations if one fails
            continue


def time_shift(
    config: str = typer.Option(..., "--config", help="Path to time-shift configuration file"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Validate configuration without execution"),
    api: bool = typer.Option(False, "--api", help="Execute via API (Lambda + ECS workers)"),
    local: bool = typer.Option(False, "--local", help="Execute locally (current behavior, default)"),
):
    """
    Time-shift existing S3 data to new time periods.

    Transforms dates in existing billing data without full regeneration.
    Provides 83% time reduction and 95% cost savings vs full regeneration.

    Execution modes:
    - --api: Submit job to Lambda API for processing on ECS workers
    - --local: Execute locally (default if neither flag specified)

    Examples:
        # Local execution
        czbg upload time-shift --config configs/upload/time-shift-2026.yaml

        # Dry run (validate only)
        czbg upload time-shift --config configs/upload/time-shift-2026.yaml --dry-run

        # API execution
        czbg upload time-shift --config configs/upload/time-shift-2026.yaml --api
    """
    # Route to API mode if --api flag provided
    if api:
        from src.upload.factories.time_shift_parallel_factory import create_parallel_executor
        import sys

        # Use parallel executor (automatically splits by provider)
        executor = create_parallel_executor()
        result = executor.execute(config_path=config)

        # Exit with error code if any drops failed
        sys.exit(0 if result.drops_failed == 0 else 1)

    from src.upload.services import (
        TimeShiftConfigLoader,
        TargetMonthParser,
        SourceMonthSelector,
        DayCountCalculator,
        CsvDateTransformer,
        ManifestManager,
        BillingPeriodHelper,
        DropCopier,
        TimeShiftProviderCopier,
        TimeShiftOrchestrator
    )
    from src.services.environment_service import EnvironmentService

    console.print("\n[bold cyan]Time-Shift Operation[/bold cyan]")
    console.print(f"Config: {config}")
    console.print(f"Mode: {'DRY RUN' if dry_run else 'EXECUTE'}\n")

    try:
        # Load environment variables from ~/.czbg/.env
        env_service = EnvironmentService()

        # Get S3 configuration from environment
        s3_bucket = env_service.require(
            "CZBILLGEN_S3_BUCKET",
            "CZBILLGEN_S3_BUCKET not configured. Add it to ~/.czbg/.env file."
        )
        s3_root_folder = "piedpiper-custom-billing"  # Hard-coded

        # Initialize services with dependency injection
        config_loader = TimeShiftConfigLoader()
        target_parser = TargetMonthParser()

        # Load config for validation/display
        ts_config = config_loader.load_config(config)

        # Display configuration summary
        console.print("[bold]Configuration:[/bold]")
        console.print(f"  Source month: {ts_config.source_month or 'auto-select'}")
        console.print(f"  Target months: {len(ts_config.target_months or [])} explicit")
        if ts_config.target_range:
            console.print(f"  Target range: {ts_config.target_range}")
        console.print(f"  Organizations: {', '.join(ts_config.organizations.keys())}")
        console.print(f"  Providers: {', '.join(ts_config.providers)}")
        console.print(f"  S3 bucket: {s3_bucket} (from ~/.czbg/.env)")
        console.print(f"  S3 root: {s3_root_folder} (hard-coded)")

        if dry_run:
            console.print("\n✅ Configuration valid", style="green")
            console.print("   Remove --dry-run to execute time-shift")
            return

        # Execute time-shift
        console.print("\n[bold]Executing Time-Shift...[/bold]\n")

        # Build orchestrator with dependencies
        source_selector = SourceMonthSelector(
            s3_bucket=s3_bucket,
            s3_root_folder=s3_root_folder
        )
        day_calculator = DayCountCalculator()
        csv_transformer = CsvDateTransformer(day_calculator)
        manifest_manager = ManifestManager(s3_bucket)
        billing_helper = BillingPeriodHelper()
        drop_copier = DropCopier(
            csv_transformer=csv_transformer,
            manifest_manager=manifest_manager,
            billing_helper=billing_helper,
            s3_bucket=s3_bucket,
            s3_root_folder=s3_root_folder
        )
        provider_copier = TimeShiftProviderCopier(
            manifest_manager=manifest_manager,
            billing_helper=billing_helper,
            s3_bucket=s3_bucket,
            s3_root_folder=s3_root_folder
        )
        orchestrator = TimeShiftOrchestrator(
            config_loader=config_loader,
            target_parser=target_parser,
            source_selector=source_selector,
            drop_copier=drop_copier,
            provider_copier=provider_copier
        )

        # Execute
        result = orchestrator.execute(config)

        # Display results
        console.print("\n[bold green]Time-Shift Complete![/bold green]\n")
        console.print(f"Job ID: {result.job_id}")
        console.print(f"Source: {result.source_month}")
        console.print(f"Targets: {len(result.target_months)} months")
        console.print(f"Drops processed: {result.drops_processed}")
        console.print(f"Drops failed: {result.drops_failed}")
        console.print(f"Total records: {result.total_records:,}")
        console.print(f"Success rate: {result.success_rate:.1f}%")
        console.print(f"Total time: {result.total_time_sec:.1f}s")
        console.print(f"Avg time per drop: {result.avg_time_per_drop:.1f}s")

        if result.drops_failed > 0:
            console.print("\n⚠️  Some drops failed:", style="yellow")
            for drop_result in result.drop_results:
                if not drop_result.success:
                    console.print(f"  • {drop_result.org}/{drop_result.provider} "
                                f"{drop_result.source_month}→{drop_result.target_month}: "
                                f"{drop_result.error_message}")
            raise typer.Exit(1)

    except FileNotFoundError as e:
        console.print(f"❌ Configuration file not found: {e}", style="red")
        raise typer.Exit(1)
    except ValueError as e:
        console.print(f"❌ Configuration error: {e}", style="red")
        raise typer.Exit(1)
    except RuntimeError as e:
        console.print(f"❌ Execution error: {e}", style="red")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"❌ Unexpected error: {e}", style="red")
        raise typer.Exit(1)


# Create Typer app with new commands
app = typer.Typer(help="Data upload operations")

app.command("generated")(upload_generated)
app.command("adhoc")(upload_adhoc)
app.command("copy-provider")(copy_provider)
app.command("time-shift")(time_shift)

# Add connections management as a sub-command group
from src.upload.cli.connections import app as connections_app
app.add_typer(connections_app, name="connections")

# Remove the old s3 command - it's now integrated into generated