"""
Focused workflow methods extracted from S3UploadOrchestrator.

Breaks down large methods into focused, testable components following
the 50-line method limit from CLAUDE.md principles.
"""

from typing import Dict, List, Any, Optional
from ..models import UploadConfig, ValidationResult, WorkflowResult, OrganizationResult


class UploadWorkflowMethods:
    """
    Focused workflow methods for upload operations.
    
    Extracted from S3UploadOrchestrator to break down god methods
    into testable, single-responsibility components.
    """
    
    def __init__(self, validation_service, data_processing_service, s3_upload_service, connection_orchestrator):
        """
        Initialize with injected services.
        
        Args:
            validation_service: Upload validation service
            data_processing_service: Data processing service
            s3_upload_service: S3 upload service
            connection_orchestrator: Connection orchestration service
        """
        self.validation_service = validation_service
        self.data_processing_service = data_processing_service
        self.s3_upload_service = s3_upload_service
        self.connection_orchestrator = connection_orchestrator
    
    def execute_upload_workflow(
        self,
        config: UploadConfig,
        dry_run: bool = False,
        coordinated_drop_id: Optional[str] = None,
        recreate_connections: bool = False,
        delete_data: bool = False
    ) -> WorkflowResult:
        """
        Execute complete upload workflow with performance optimization.

        Replaces 140-line upload_dataset_to_orgs method with focused coordination.

        Args:
            config: Upload configuration
            dry_run: Whether to perform dry run validation only
            coordinated_drop_id: Optional coordinated drop ID from integrated append workflow
            recreate_connections: Whether to recreate connections for secondary orgs (multi-org copy)
            delete_data: Whether data was deleted during recreation (Mode 3 vs Mode 2)

        Returns:
            WorkflowResult with upload outcomes
        """
        # Step 1: Initialize and validate preconditions
        validation_result = self._validate_upload_preconditions(config, dry_run)
        if not validation_result.is_successful():
            return self._format_validation_failure_result(validation_result, config)

        # Step 2: Execute organization processing
        processing_results = self._execute_organization_processing(
            config, validation_result.details["processing_plan"], dry_run,
            coordinated_drop_id, recreate_connections, delete_data
        )

        # Step 3: Format and return final results
        return self._format_final_workflow_result(config, processing_results, dry_run)
    
    def execute_adhoc_upload_workflow(self, config: UploadConfig, dry_run: bool = False) -> WorkflowResult:
        """
        Execute ad-hoc CSV upload workflow.
        
        Replaces 135-line upload_adhoc_to_orgs method with focused coordination.
        
        Args:
            config: Upload configuration with ad-hoc CSV details
            dry_run: Whether to perform dry run validation only
            
        Returns:
            WorkflowResult with upload outcomes
        """
        # Step 1: Validate and load ad-hoc dataset
        dataset_result = self._validate_and_load_adhoc_dataset(config)
        if dataset_result["status"] != "success":
            return self._format_adhoc_failure_result(dataset_result, config)
        
        # Step 2: Process organizations with ad-hoc data
        processing_results = self._execute_adhoc_organization_processing(
            config, dataset_result["processed_data"], dry_run
        )
        
        # Step 3: Format final results
        return self._format_final_workflow_result(config, processing_results, dry_run, mode="adhoc")
    
    def execute_recreate_workflow(self, config: UploadConfig, delete_data: bool = False) -> WorkflowResult:
        """
        Execute connection recreation followed by upload.

        For multi-org copy optimization, recreates primary org connection before
        upload, then recreates secondary org connections before copying.

        Phase 2 Modes:
        - Mode 2 (delete_data=False): Recreate connections, preserve data, skip if exists
        - Mode 3 (delete_data=True): Recreate connections + delete data, always reprocess

        Args:
            config: Upload configuration
            delete_data: Whether to delete S3 data during recreation (Mode 3)

        Returns:
            WorkflowResult with recreation and upload outcomes
        """
        # Step 1: Execute connection recreation for primary org
        recreation_results = self._execute_connection_recreation(config, delete_data=delete_data)

        # Step 2: Execute upload with recreation flag for secondary orgs
        if recreation_results["overall_status"] != "failed":
            upload_result = self.execute_upload_workflow(
                config, dry_run=False, recreate_connections=True, delete_data=delete_data
            )
            return self._combine_recreation_and_upload_results(recreation_results, upload_result, config)
        else:
            return self._format_recreation_failure_result(recreation_results, config)
    
    def _validate_upload_preconditions(self, config: UploadConfig, dry_run: bool) -> ValidationResult:
        """
        Validate all preconditions for upload.
        
        Args:
            config: Upload configuration
            dry_run: Whether this is a dry run
            
        Returns:
            ValidationResult with validation outcome
        """
        print(f"🔍 Performing unified validation...")
        return self.validation_service.perform_unified_validation(config, dry_run)
    
    def _execute_organization_processing(
        self,
        config: UploadConfig,
        processing_plan: Dict[str, Any],
        dry_run: bool,
        coordinated_drop_id: Optional[str] = None,
        recreate_connections: bool = False,
        delete_data: bool = False
    ) -> Dict[str, OrganizationResult]:
        """
        Execute processing for all organizations.

        Args:
            config: Upload configuration
            processing_plan: Validated processing plan
            dry_run: Whether this is a dry run
            coordinated_drop_id: Optional coordinated drop ID from integrated workflow
            recreate_connections: Whether to recreate connections for secondary orgs
            delete_data: Whether data was deleted during recreation (Mode 3 vs Mode 2)

        Returns:
            Dict mapping organization names to processing results
        """
        if self._should_use_copy_optimization(config):
            print("🎯 Multi-org copy optimization enabled")
            return self._execute_multi_org_copy_workflow(
                config, processing_plan, dry_run, coordinated_drop_id, recreate_connections
            )

        return self._execute_sequential_upload_workflow(
            config, processing_plan, dry_run, coordinated_drop_id, delete_data
        )

    def _execute_sequential_upload_workflow(
        self,
        config: UploadConfig,
        processing_plan: Dict[str, Any],
        dry_run: bool,
        coordinated_drop_id: Optional[str],
        delete_data: bool = False
    ) -> Dict[str, OrganizationResult]:
        """Execute standard sequential upload workflow for all organizations."""
        results = {}
        total_orgs = len(config.organizations)

        for i, org_plan in enumerate(processing_plan.organizations, 1):
            org_name = org_plan.name
            org = self._find_organization_by_name(config.organizations, org_name)

            print(f"\n🏢 {'Validating' if dry_run else 'Processing'} organization {i}/{total_orgs}: {org_name}")

            if not self._is_organization_ready_for_processing(org_plan):
                results[org_name] = self._create_failed_org_result(org_name, org_plan.error or "Not ready")
                continue

            org_result = self._process_single_organization(org, org_plan, config, dry_run, coordinated_drop_id, delete_data)
            results[org_name] = org_result

            self._log_organization_result(org_name, org_result, dry_run)

        return results

    def _process_single_organization(self, org, org_plan, config: UploadConfig, dry_run: bool, coordinated_drop_id: Optional[str], delete_data: bool = False) -> OrganizationResult:
        """Process single organization based on mode (dry-run or upload)."""
        if dry_run:
            return self._process_dry_run_organization(org, org_plan)
        else:
            return self._process_upload_organization(org, org_plan, config, coordinated_drop_id, delete_data)

    def _log_organization_result(self, org_name: str, org_result: OrganizationResult, dry_run: bool) -> None:
        """Log organization processing result."""
        status_icon = "✅" if org_result.is_successful() else "❌"
        action = "Validation" if dry_run else "Upload"
        status_text = "completed" if org_result.is_successful() else "failed"
        print(f"{status_icon} {org_name}: {action} {status_text}")
    
    def _validate_and_load_adhoc_dataset(self, config: UploadConfig) -> Dict[str, Any]:
        """
        Validate S3 access and load ad-hoc dataset.
        
        Args:
            config: Upload configuration with ad-hoc details
            
        Returns:
            Dict with validation and loading results
        """
        try:
            # Validate S3 bucket access
            if not self.s3_upload_service.validate_bucket_access(config.s3_bucket, config.s3_region):
                return {"status": "failed", "error": "S3 bucket validation failed"}
            
            # Load ad-hoc dataset
            raw_df = self.data_processing_service.load_adhoc_dataset_from_csv(config.adhoc_csv_path)
            
            # Load user mapping if provided
            user_mapping = []
            if config.adhoc_mapping_path:
                user_mapping = self.data_processing_service.load_user_mapping(config.adhoc_mapping_path)
            
            # Process ad-hoc dataset
            processing_result = self.data_processing_service.process_adhoc_dataset(raw_df, user_mapping)
            
            if not processing_result.is_successful():
                return {"status": "failed", "error": processing_result.error_message}
            
            return {"status": "success", "processed_data": processing_result.processed_data}
            
        except Exception as e:
            return {"status": "failed", "error": f"Failed to load/process ad-hoc dataset: {str(e)}"}
    
    def _execute_connection_recreation(self, config: UploadConfig, delete_data: bool = False) -> Dict[str, Any]:
        """
        Execute connection recreation for primary organization only.

        Secondary orgs will have connections recreated during copy workflow to avoid
        redundant recreation operations.

        Args:
            config: Upload configuration
            delete_data: Whether to delete S3 data during recreation (Mode 3)

        Returns:
            Recreation results summary
        """
        recreation_results = []
        successful_recreations = 0

        # Only recreate primary org connection (first in list)
        # Secondary orgs will be recreated during copy workflow
        primary_org = config.organizations[0]
        mode = "Mode 3" if delete_data else "Mode 2"
        print(f"\n  {mode}: Primary Organization: {primary_org.name}")

        result = self.connection_orchestrator.recreate_organization_connections(
            primary_org.name, primary_org.providers, self._build_s3_config_dict(config, primary_org.name),
            dry_run=False, delete_data=delete_data, connection_suffix=config.connection_suffix
        )

        recreation_results.append(result)
        if result["status"] == "success":
            successful_recreations += 1

        # Note: Secondary orgs will be recreated per-provider during copy workflow
        if len(config.organizations) > 1:
            print(f"\n  Note: {len(config.organizations) - 1} secondary org(s) will have connections recreated during copy")

        return self._format_recreation_summary(recreation_results, successful_recreations)
    
    def _format_validation_failure_result(self, validation_result: ValidationResult, config: UploadConfig) -> WorkflowResult:
        """Format validation failure into WorkflowResult."""
        return WorkflowResult(
            status="failed",
            dataset=config.dataset_name,
            organizations_processed=0,
            organizations_successful=0,
            results={},
            error_message=validation_result.error_message,
            mode="validation_failed"
        )
    
    def _format_final_workflow_result(
        self, 
        config: UploadConfig, 
        processing_results: Dict[str, OrganizationResult], 
        dry_run: bool,
        mode: str = "upload"
    ) -> WorkflowResult:
        """Format final workflow results."""
        successful_orgs = sum(1 for result in processing_results.values() if result.is_successful())
        total_orgs = len(processing_results)
        
        # Determine overall status
        if successful_orgs == total_orgs:
            status = "success"
        elif successful_orgs > 0:
            status = "partial"
        else:
            status = "failed"
        
        return WorkflowResult(
            status=status,
            dataset=config.dataset_name,
            organizations_processed=total_orgs,
            organizations_successful=successful_orgs,
            results=processing_results,
            mode="dry_run" if dry_run else mode
        )
    
    def _is_organization_ready_for_processing(self, org_plan) -> bool:
        """Check if organization is ready for processing."""
        return org_plan.is_ready_for_processing()
    
    def _find_organization_by_name(self, organizations: List, org_name: str):
        """Find organization by name."""
        return next(org for org in organizations if org.name == org_name)
    
    def _create_failed_org_result(self, org_name: str, error: str) -> OrganizationResult:
        """Create failed organization result."""
        return OrganizationResult(
            organization=org_name,
            status="failed",
            error_message=error
        )
    
    def _process_dry_run_organization(self, org, org_plan) -> OrganizationResult:
        """Process organization in dry-run mode (validation only)."""
        print(f"   🔍 Dry-run validation for {org.name}")
        print(f"   📊 Providers: {len(org_plan.providers)}")
        print(f"   📁 Files planned: {sum(p.file_count for p in org_plan.providers)}")
        
        # Return successful dry-run result
        return OrganizationResult(
            organization=org.name,
            status="success",
            files_uploaded=sum(p.file_count for p in org_plan.providers),
            total_rows=0,  # No actual processing in dry-run
            providers=[p.name for p in org_plan.providers]
        )
    
    def _process_upload_organization(self, org, org_plan, config: UploadConfig, coordinated_drop_id: Optional[str] = None, delete_data: bool = False) -> OrganizationResult:
        """Process organization for actual upload."""
        print(f"   🚀 Processing {org.name}")

        drop_id = self._get_or_generate_drop_id(coordinated_drop_id)

        try:
            upload_results, total_files, total_rows, failed_files = self._upload_all_providers(
                org, org_plan, config, drop_id, delete_data
            )
            self._update_manifests_for_successful_uploads(org.name, upload_results, drop_id, config.connection_suffix)
            status = self._determine_upload_status(org.name, total_files, failed_files)

            return self._build_organization_result(
                org, org_plan, status, drop_id, total_files, total_rows,
                failed_files, upload_results
            )

        except Exception as e:
            print(f"   ❌ {org.name}: Processing failed - {str(e)}")
            return OrganizationResult(
                organization=org.name,
                status="failed",
                error_message=str(e),
                providers=[p.name for p in org_plan.providers]
            )

    def _get_or_generate_drop_id(self, coordinated_drop_id: Optional[str]) -> str:
        """Get coordinated drop ID or generate new one."""
        if coordinated_drop_id:
            return coordinated_drop_id

        from ..s3.formatter import S3Formatter
        s3_formatter = S3Formatter()
        return s3_formatter.generate_drop_id()

    def _upload_all_providers(self, org, org_plan, config: UploadConfig, drop_id: str, delete_data: bool = False):
        """Upload files for all providers in organization plan."""
        upload_results = []
        total_files = 0
        total_rows = 0
        failed_files = []

        # Initialize SmartSkipDecider for Mode 2 skip logic
        from .data_lifecycle_manager import DataLifecycleManager
        from .smart_skip_decider import SmartSkipDecider
        from src.services.environment_service import EnvironmentService

        env_service = EnvironmentService()
        s3_bucket = env_service.require("CZBILLGEN_S3_BUCKET")
        data_lifecycle = DataLifecycleManager(s3_bucket=s3_bucket)
        skip_decider = SmartSkipDecider(data_lifecycle)

        for provider_plan in org_plan.providers:
            # Check if we should skip processing (Mode 2 only)
            should_skip, skip_reason = skip_decider.should_skip_processing(
                org=org.name,
                provider=provider_plan.name,
                suffix=config.connection_suffix,
                delete_data=delete_data
            )

            if should_skip:
                print(f"     ⏩ {provider_plan.name}: {skip_reason}")
                continue

            print(f"     📁 {provider_plan.name}: {len(provider_plan.months)} months, {provider_plan.file_count} files")

            provider_results = self.s3_upload_service.stream_upload_files(
                dataset_name=config.dataset_name,
                provider=provider_plan.name,
                months=provider_plan.months,
                stage=provider_plan.stage,
                organization=org.name,
                drop_id=drop_id,
                connection_suffix=config.connection_suffix
            )

            # Aggregate results
            for result in provider_results:
                upload_results.append(result)
                if result.is_successful():
                    total_files += 1
                    total_rows += result.rows
                else:
                    failed_files.append({
                        "provider": provider_plan.name,
                        "month": result.month,
                        "error": result.error_message
                    })
                    print(f"     ❌ {provider_plan.name} {result.month}: {result.error_message}")

        return upload_results, total_files, total_rows, failed_files

    def _update_manifests_for_successful_uploads(
        self,
        org_name: str,
        upload_results: List,
        drop_id: str,
        connection_suffix: Optional[str] = None
    ) -> None:
        """Update manifests and prune old drops for successful uploads."""
        successful_uploads = [result for result in upload_results if result.is_successful()]
        if not successful_uploads:
            return

        print(f"   🗂️  Updating manifests and pruning old drops...")
        prune_errors = self.s3_upload_service.update_manifests_and_prune_for_uploads(
            org_name, successful_uploads, drop_id, connection_suffix
        )
        if prune_errors:
            for error in prune_errors:
                print(f"      ⚠️  {error}")

    def _determine_upload_status(self, org_name: str, total_files: int, failed_files: List) -> str:
        """Determine overall upload status based on results."""
        if not failed_files:
            print(f"   ✅ {org_name}: {total_files} files uploaded successfully")
            return "success"
        elif len(failed_files) < total_files:
            print(f"   ⚠️  {org_name}: {total_files - len(failed_files)}/{total_files} files uploaded")
            return "partial"
        else:
            print(f"   ❌ {org_name}: All files failed to upload")
            return "failed"

    def _build_organization_result(
        self, org, org_plan, status: str, drop_id: str,
        total_files: int, total_rows: int, failed_files: List, upload_results: List
    ) -> OrganizationResult:
        """Build OrganizationResult from upload outcomes."""
        return OrganizationResult(
            organization=org.name,
            status=status,
            drop_id=drop_id,
            files_uploaded=total_files,
            files_failed=len(failed_files),
            total_rows=total_rows,
            providers=[p.name for p in org_plan.providers],
            uploaded_files=upload_results,
            failed_files=failed_files
        )
    
    def _build_s3_config_dict(self, config: UploadConfig, org_name: str) -> Dict[str, str]:
        """Build S3 configuration dictionary."""
        return {
            "bucket_name": config.s3_bucket,
            "root_folder": "piedpiper-custom-billing",  # Generated uploads use this folder
            "org_folder": org_name
        }
    
    def _format_recreation_summary(self, recreation_results: List[Dict[str, Any]], successful_recreations: int) -> Dict[str, Any]:
        """Format recreation results into summary."""
        total_orgs = len(recreation_results)
        
        return {
            "total_orgs": total_orgs,
            "successful_orgs": successful_recreations,
            "failed_orgs": total_orgs - successful_recreations,
            "overall_status": "success" if successful_recreations == total_orgs else "partial" if successful_recreations > 0 else "failed",
            "details": recreation_results
        }
    
    def _combine_recreation_and_upload_results(
        self, 
        recreation_results: Dict[str, Any], 
        upload_result: WorkflowResult, 
        config: UploadConfig
    ) -> WorkflowResult:
        """Combine recreation results with upload results."""
        # Determine combined status
        if recreation_results["overall_status"] == "failed":
            combined_status = "failed"
        elif upload_result.is_failed():
            combined_status = "failed"
        elif recreation_results["overall_status"] == "partial" or upload_result.is_partial():
            combined_status = "partial"  
        else:
            combined_status = "success"
        
        # Create combined result
        return WorkflowResult(
            status=combined_status,
            dataset=config.dataset_name,
            organizations_processed=upload_result.organizations_processed,
            organizations_successful=upload_result.organizations_successful,
            results=upload_result.results,
            mode="recreate_and_upload",
            error_message=upload_result.error_message
        )
    
    def _format_recreation_failure_result(self, recreation_results: Dict[str, Any], config: UploadConfig) -> WorkflowResult:
        """Format recreation failure into WorkflowResult."""
        return WorkflowResult(
            status="failed",
            dataset=config.dataset_name,
            organizations_processed=recreation_results["total_orgs"],
            organizations_successful=recreation_results["successful_orgs"],
            results={},
            error_message=f"Connection recreation failed for {recreation_results['failed_orgs']} organizations",
            mode="recreation_failed"
        )
    def _should_use_copy_optimization(self, config: UploadConfig) -> bool:
        """
        Check if multi-org copy optimization should be used.

        Criteria:
        - 2+ organizations configured
        - All orgs have identical providers
        - All orgs have identical months
        - Not explicitly disabled

        Returns:
            True if optimization should be used
        """
        if len(config.organizations) < 2:
            return False

        # Validate all orgs have identical provider/month config
        first_org = config.organizations[0]
        for org in config.organizations[1:]:
            if org.providers != first_org.providers:
                return False
            if org.months != first_org.months:
                return False

        return True

    def _execute_multi_org_copy_workflow(
        self,
        config: UploadConfig,
        processing_plan: Dict[str, Any],
        dry_run: bool,
        coordinated_drop_id: Optional[str],
        recreate_connections: bool = False
    ) -> Dict[str, OrganizationResult]:
        """
        Execute optimized multi-org workflow with copy.

        Process:
        1. Upload to primary org (first in list)
        2. For each secondary org: recreate connection (if enabled) + copy from primary

        Args:
            config: Upload configuration
            processing_plan: Validated processing plan
            dry_run: Whether this is a dry run
            coordinated_drop_id: Optional coordinated drop ID
            recreate_connections: Whether to recreate connections for secondary orgs
        """
        results = {}

        # Step 1: Upload to primary org
        primary_org = config.organizations[0]
        primary_plan = processing_plan.organizations[0]

        print(f"\n🏢 Processing primary organization: {primary_org.name}")
        primary_result = self._process_upload_organization(
            primary_org, primary_plan, config, coordinated_drop_id
        )
        results[primary_org.name] = primary_result

        if not primary_result.is_successful():
            print(f"❌ Primary upload failed - skipping secondary orgs")
            return results

        print(f"✅ Primary upload complete")
        print(f"🔄 Copying to {len(config.organizations) - 1} secondary orgs")

        # Step 2: Copy to secondary orgs (with recreation if enabled)
        for i, secondary_org in enumerate(config.organizations[1:], 2):
            print(f"\n🏢 Copying to organization {i}/{len(config.organizations)}: {secondary_org.name}")

            secondary_result = self._copy_from_primary_org(
                primary_org=primary_org,
                primary_result=primary_result,
                target_org=secondary_org,
                config=config,
                dry_run=dry_run,
                recreate_connection=recreate_connections
            )
            results[secondary_org.name] = secondary_result

        return results

    def _copy_from_primary_org(
        self,
        primary_org,
        primary_result: OrganizationResult,
        target_org,
        config: UploadConfig,
        dry_run: bool,
        recreate_connection: bool = False
    ) -> OrganizationResult:
        """
        Copy uploaded data from primary org to target org.

        Uses ProviderCopyOrchestrator for each provider.

        Args:
            primary_org: Primary organization (source)
            primary_result: Result from primary org upload
            target_org: Target organization to copy to
            config: Upload configuration
            dry_run: Whether this is a dry run
            recreate_connection: Whether to recreate connection before copy
        """
        from .provider_copy_orchestrator import ProviderCopyOrchestratorFactory

        orchestrator = ProviderCopyOrchestratorFactory.create()

        # Copy each provider from primary to target
        total_files_copied = 0
        copy_errors = []

        for provider in target_org.providers:
            try:
                copy_result = orchestrator.copy_provider_to_org(
                    source_org=primary_org.name,
                    target_org=target_org.name,
                    provider=provider,
                    billing_periods=None,  # Copy all
                    dry_run=dry_run,
                    create_connection=True,
                    recreate_connection=recreate_connection,
                    connection_suffix=config.connection_suffix
                )

                if copy_result.status == "success":
                    total_files_copied += copy_result.files_copied
                    print(f"   ✓ {provider}: {copy_result.files_copied} files copied")
                else:
                    copy_errors.extend(copy_result.copy_errors)
                    print(f"   ❌ {provider}: Copy failed")

            except Exception as e:
                error_msg = f"{provider} copy failed: {str(e)}"
                copy_errors.append(error_msg)
                print(f"   ❌ {error_msg}")

        # Build organization result
        if copy_errors:
            status = "failed" if total_files_copied == 0 else "partial"
        else:
            status = "success"

        return OrganizationResult(
            organization=target_org.name,
            status=status,
            files_uploaded=total_files_copied,
            providers=target_org.providers,
            drop_id=primary_result.drop_id,  # Same drop_id as primary
            error_message="; ".join(copy_errors) if copy_errors else None
        )
