"""
Month-by-month processing orchestrator for K8s allocation.

Handles the complete month-by-month processing pipeline:
1. Discover available months from S3
2. Process each month individually
3. Memory-efficient operations with immediate cleanup
4. Progress tracking and error handling
"""

from typing import List, Dict, Any, Optional
from datetime import datetime
import polars as pl
from rich.console import Console
from rich.progress import Progress, TaskID

from src.log_context import get_logger
from ..config import K8sConfig, CLUSTERS_COUNT, NAMESPACES_PER_CLUSTER, WORKLOADS_PER_NAMESPACE, MIN_RESOURCES_PER_WORKLOAD
from .k8s_data_service import K8sDataService
from .processor_service import K8sProcessorService
from .compute_filter_service import ComputeFilterService
from .column_mapper import column_mapper


class MonthProcessor:
    """
    Orchestrates month-by-month processing for K8s allocation.
    
    Provides memory-efficient processing by handling one month at a time
    with immediate cleanup and comprehensive progress tracking.
    """
    
    def __init__(self, config: K8sConfig, console: Console, debug: bool = False):
        self.config = config
        self.console = console
        self.debug = debug
        self.logger = get_logger(__name__)
        
        # Initialize service components
        self.data_service = K8sDataService()
        self.processor_service = K8sProcessorService(config)
        
        # Processing state
        self.processing_summary = {
            "total_months": 0,
            "processed_months": 0,
            "failed_months": 0,
            "total_input_cost": 0.0,
            "total_output_cost": 0.0,
            "providers_processed": set(),
            "months_processed": []
        }
        
        # Early resource validation state - track provider-month combinations
        self.validated_provider_months = set()
        self.validation_failures = []
        self.REQUIRED_PROVIDERS = {"AWS", "Azure", "GCP"}
        
        self.logger.info(
            "Initialized MonthProcessor",
            dataset_name=config.dataset_name
        )
    
    def discover_months(self, target_month: Optional[str] = None, target_provider: Optional[str] = None) -> List[Dict[str, str]]:
        """
        Discover available months for processing.
        
        Args:
            target_month: Optional specific month to process (YYYY-MM)
            target_provider: Optional specific provider to process (AWS/GCP/Azure)
            
        Returns:
            List of dictionaries with provider and month information
        """
        if target_month and target_provider:
            # Process specific month and provider
            self.console.print(f"🔍 Discovering data for {target_provider} {target_month}")
            
            months_to_process = []
            available_months = self.data_service.list_available_months(
                self.config.dataset_name, target_provider
            )
            
            if target_month in available_months:
                months_to_process.append({
                    "provider": target_provider,
                    "month": target_month
                })
                self.logger.debug(
                    "Found target month for provider",
                    provider=target_provider,
                    month=target_month
                )
            
            if not months_to_process:
                self.console.print(f"⚠️  [yellow]No data found for {target_provider} {target_month}[/yellow]")
                
        elif target_month:
            # Process specific month for all providers
            self.console.print(f"🔍 Discovering providers for month: {target_month}")
            
            months_to_process = []
            providers = [target_provider] if target_provider else ["AWS", "GCP", "Azure"]
            
            for provider in providers:
                available_months = self.data_service.list_available_months(
                    self.config.dataset_name, provider
                )
                
                if target_month in available_months:
                    months_to_process.append({
                        "provider": provider,
                        "month": target_month
                    })
                    self.logger.debug(
                        "Found target month for provider",
                        provider=provider,
                        month=target_month
                    )
            
            if not months_to_process:
                self.console.print(f"⚠️  [yellow]No data found for month {target_month}[/yellow]")
                
        elif target_provider:
            # Process specific provider for all months
            self.console.print(f"🔍 Discovering months for provider: {target_provider}")
            
            months_to_process = []
            available_months = self.data_service.list_available_months(
                self.config.dataset_name, target_provider
            )
            
            for month in available_months:
                months_to_process.append({
                    "provider": target_provider,
                    "month": month
                })
            
            self.logger.debug(
                "Found months for provider",
                provider=target_provider,
                month_count=len(available_months)
            )
                
        else:
            # Process all available months
            self.console.print("🔍 Discovering available months across all providers")
            
            months_to_process = []
            for provider in ["AWS", "GCP", "Azure"]:
                available_months = self.data_service.list_available_months(
                    self.config.dataset_name, provider
                )
                
                for month in available_months:
                    months_to_process.append({
                        "provider": provider,
                        "month": month
                    })
                
                self.logger.debug(
                    "Found months for provider",
                    provider=provider,
                    month_count=len(available_months)
                )
        
        # Sort by month then provider for consistent processing order
        months_to_process.sort(key=lambda x: (x["month"], x["provider"]))
        
        self.processing_summary["total_months"] = len(months_to_process)
        
        self.console.print(f"📅 Found {len(months_to_process)} month-provider combinations to process")
        
        return months_to_process
    
    def process_single_month(self, provider: str, month: str) -> Dict[str, Any]:
        """
        Process a single month of data.
        
        Args:
            provider: Provider name (AWS/GCP/Azure)
            month: Month string (YYYY-MM)
            
        Returns:
            Processing result dictionary
        """
        self.logger.info(
            "Processing month",
            provider=provider,
            month=month,
            dataset_name=self.config.dataset_name
        )
        
        processing_result = {
            "success": False,
            "provider": provider,
            "month": month,
            "input_rows": 0,
            "output_rows": 0,
            "input_cost": 0.0,
            "output_cost": 0.0,
            "processing_time": 0.0,
            "error": None,
            "validation_report": None
        }
        
        start_time = datetime.now()
        
        try:
            # Step 1: Download month data (already processed check moved to main processing loop)
            if self.debug:
                self.console.print(f"🔍 [cyan]Step 1: Downloading {provider} {month} data from S3[/cyan]")
            
            month_data = self.data_service.download_month_data(
                self.config.dataset_name, provider, month
            )
            
            if month_data is None or month_data.is_empty():
                self.logger.warning(
                    "No data available for month",
                    provider=provider,
                    month=month
                )
                processing_result["error"] = "No data available"
                return processing_result
            
            processing_result["input_rows"] = len(month_data)
            
            # Find cost column using ColumnMapper
            cost_column = column_mapper.get_column_name(month_data, "Cost")
            if not cost_column:
                processing_result["error"] = "Cost column not found in data"
                self.logger.error(
                    "Cost column missing from month data",
                    provider=provider,
                    month=month,
                    columns=month_data.columns
                )
                return processing_result
            
            try:
                processing_result["input_cost"] = float(month_data.get_column(cost_column).sum())
                if self.debug:
                    self.console.print(f"🔍 [cyan]Step 1b: Data loaded - {len(month_data)} rows, ${processing_result['input_cost']:,.2f} cost[/cyan]")
                    # Show compute vs non-compute breakdown
                    from .compute_filter_service import ComputeFilterService
                    temp_filter = ComputeFilterService(self.config)
                    compute_mask = temp_filter.create_compute_mask(month_data)
                    compute_count = compute_mask.sum()
                    self.console.print(f"🔍 [cyan]Step 1c: Compute resources: {compute_count}/{len(month_data)} ({compute_count/len(month_data):.1%})[/cyan]")
            except Exception as e:
                processing_result["error"] = f"Failed to calculate input cost: {str(e)}"
                self.logger.error(
                    "Failed to calculate input cost",
                    provider=provider,
                    month=month,
                    cost_column=cost_column,
                    cost_column_dtype=month_data.get_column(cost_column).dtype,
                    error=str(e)
                )
                return processing_result
            
            # Step 2: Process with K8s allocation
            if self.debug:
                self.console.print(f"🔍 [cyan]Step 2: Processing {provider} {month} with K8s allocation[/cyan]")
            
            processed_data = self.processor_service.process_month_data(month_data, provider)
            
            if self.debug:
                k8s_rows = processed_data.filter(pl.col("k8s_cluster").is_not_null())
                non_k8s_rows = processed_data.filter(pl.col("k8s_cluster").is_null())
                self.console.print(f"🔍 [cyan]Step 2b: Processing complete - {len(processed_data)} output rows[/cyan]")
                self.console.print(f"🔍 [cyan]Step 2c: K8s rows: {len(k8s_rows)}, Non-K8s rows: {len(non_k8s_rows)}[/cyan]")
                if len(k8s_rows) > 0:
                    expansion_factor = len(k8s_rows) / len(month_data) if len(month_data) > 0 else 0
                    self.console.print(f"🔍 [cyan]Step 2d: Row expansion factor: {expansion_factor:.1f}x[/cyan]")
            
            processing_result["output_rows"] = len(processed_data)
            
            # Find cost column in processed data using ColumnMapper
            output_cost_column = column_mapper.get_column_name(processed_data, "Cost")
            if not output_cost_column:
                processing_result["error"] = "Cost column not found in processed data"
                self.logger.error(
                    "Cost column missing from processed data",
                    provider=provider,
                    month=month,
                    columns=processed_data.columns
                )
                return processing_result
            
            try:
                processing_result["output_cost"] = float(processed_data.get_column(output_cost_column).sum())
            except Exception as e:
                processing_result["error"] = f"Failed to calculate output cost: {str(e)}"
                self.logger.error(
                    "Failed to calculate output cost",
                    provider=provider,
                    month=month,
                    cost_column=output_cost_column,
                    cost_column_dtype=processed_data.get_column(output_cost_column).dtype,
                    error=str(e)
                )
                return processing_result
            
            # Step 3: Validate K8s processing results
            validation_report = self._validate_k8s_processing_result(
                month_data, processed_data, provider, month
            )
            if not validation_report["validation_passed"]:
                self._display_validation_failures(validation_report, provider, month)
                processing_result["error"] = "K8s validation failed"
                processing_result["validation_report"] = validation_report
                # Set flag to indicate validation failure for fail-fast behavior
                processing_result["validation_failed"] = True
                return processing_result
            
            # Step 4: Upload processed data
            if self.debug:
                self.console.print(f"🔍 [cyan]Step 4: Uploading {provider} {month} processed data to S3[/cyan]")
            
            topology_summary = self.processor_service.get_topology_summary()
            validation_report = {
                "processed_at": datetime.now().isoformat(),
                "input_rows": processing_result["input_rows"],
                "output_rows": processing_result["output_rows"],
                "input_cost": processing_result["input_cost"],
                "output_cost": processing_result["output_cost"],
                "topology_summary": topology_summary,
                "config": self.config.to_dict()
            }
            
            upload_success = self.data_service.upload_month_data(
                self.config.dataset_name,
                provider,
                month,
                processed_data,
                validation_report
            )
            
            if self.debug:
                self.console.print(f"🔍 [cyan]Step 4b: Upload {'successful' if upload_success else 'failed'}[/cyan]")
            
            if not upload_success:
                processing_result["error"] = "Failed to upload processed data"
                return processing_result
            
            # Step 5: Cleanup local files
            if self.debug:
                self.console.print(f"🔍 [cyan]Step 5: Cleaning up local cache for {provider} {month}[/cyan]")
            
            # Pass validation status to cleanup - preserve validation files if validation failed
            validation_passed = validation_report.get("validation_passed", True)
            self.data_service.cleanup_local_cache(
                self.config.dataset_name, provider, month, validation_passed=validation_passed
            )
            
            processing_result["success"] = True
            processing_result["validation_report"] = validation_report
            
        except ValueError as ve:
            # Check if this is a constraint violation - these should fail fast
            if "exceed maximum resource requirements" in str(ve) or "do not meet minimum resource requirements" in str(ve):
                self.logger.error(
                    "CRITICAL: K8s constraint violation detected - failing fast",
                    provider=provider,
                    month=month,
                    error=str(ve)
                )
                # Re-raise constraint violations to fail the entire process
                raise ve
            else:
                # Handle other ValueErrors normally
                self.logger.error(
                    "Failed to process month - value error",
                    provider=provider,
                    month=month,
                    error=str(ve),
                    exc_info=True
                )
                processing_result["error"] = str(ve)
        except Exception as e:
            self.logger.error(
                "Failed to process month",
                provider=provider,
                month=month,
                error=str(e),
                exc_info=True
            )
            processing_result["error"] = str(e)
            
            # Check if error is related to insufficient resources (early validation failure)
            if "Insufficient resources for K8s allocation" in str(e):
                processing_result["validation_failed"] = True
                self.logger.error(
                    "Early validation failed - insufficient resources",
                    provider=provider,
                    month=month,
                    error=str(e)
                )
            
        finally:
            end_time = datetime.now()
            processing_result["processing_time"] = (end_time - start_time).total_seconds()
        
        return processing_result
    
    def process_all_months(self, target_month: Optional[str] = None, target_provider: Optional[str] = None) -> Dict[str, Any]:
        """
        Process all available months with progress tracking.
        
        Args:
            target_month: Optional specific month to process
            target_provider: Optional specific provider to process
            
        Returns:
            Complete processing summary
        """
        self.console.print("🚀 [bold green]Starting month-by-month K8s processing[/bold green]")
        
        # Discover months to process
        months_to_process = self.discover_months(target_month, target_provider)
        
        if not months_to_process:
            self.console.print("❌ [red]No months found to process[/red]")
            return self.processing_summary
        
        failed_months = []
        
        # Process each month with progress tracking
        with Progress(console=self.console) as progress:
            task = progress.add_task(
                "[cyan]Processing months...", 
                total=len(months_to_process)
            )
            
            for month_info in months_to_process:
                provider = month_info["provider"]
                month = month_info["month"]
                
                # Update progress description
                progress.update(
                    task, 
                    description=f"[cyan]Processing {provider} {month}..."
                )
                
                # Check if month already processed before doing expensive validation
                if self.data_service.check_month_exists(self.config.dataset_name, provider, month):
                    self.logger.info(
                        "Month already processed, skipping validation and processing",
                        provider=provider,
                        month=month
                    )
                    progress.update(task, advance=1)
                    continue
                
                # Resource validation for each provider-month combination
                provider_month_key = f"{provider}:{month}"
                if provider in self.REQUIRED_PROVIDERS and provider_month_key not in self.validated_provider_months:
                    if not self._validate_provider_resource_requirements(provider, month):
                        # Resource validation failed for this specific provider-month
                        self.console.print(f"⚠️  [yellow]Skipping {provider} {month} due to insufficient resources[/yellow]")
                        progress.update(task, advance=1)
                        continue
                    else:
                        # Mark this provider-month combination as validated
                        self.validated_provider_months.add(provider_month_key)
                
                # Process single month
                result = self.process_single_month(provider, month)
                
                # Update processing summary
                if result["success"]:
                    self.processing_summary["processed_months"] += 1
                    self.processing_summary["total_input_cost"] += result["input_cost"]
                    self.processing_summary["total_output_cost"] += result["output_cost"]
                    self.processing_summary["providers_processed"].add(provider)
                    self.processing_summary["months_processed"].append(f"{provider}-{month}")
                    
                    if result.get("skipped"):
                        self.console.print(f"⏭️  [yellow]Skipped {provider} {month} (already processed)[/yellow]")
                    else:
                        self.console.print(f"✅ [green]Completed {provider} {month}[/green]")
                        
                else:
                    self.processing_summary["failed_months"] += 1
                    failed_months.append(result)
                    self.console.print(f"❌ [red]Failed {provider} {month}: {result['error']}[/red]")
                    
                    # Fail-fast behavior: Stop processing immediately when ANY month fails
                    self.console.print("🛑 [red bold]FAIL-FAST: Stopping processing due to month failure[/red bold]")
                    self.console.print("📋 [yellow]Processing stops immediately when any month fails[/yellow]")
                    
                    # Cancel remaining tasks
                    remaining_months = len(months_to_process) - (months_to_process.index(month_info) + 1)
                    if remaining_months > 0:
                        self.console.print(f"⏸️  [yellow]Skipping {remaining_months} remaining months due to failure[/yellow]")
                    
                    # DO NOT artificially advance progress - show actual completion
                    # Progress bar will show real percentage: processed_months / total_months
                    break
                
                # Update progress
                progress.update(task, advance=1)
        
        # Write SUCCESS marker if all months processed successfully
        if self.processing_summary["failed_months"] == 0 and self.processing_summary["processed_months"] > 0:
            self._write_success_marker()
        
        # Display final summary
        self._display_processing_summary(failed_months)
        
        return self.processing_summary
    
    def _write_success_marker(self) -> None:
        """Write SUCCESS marker after all months processed successfully."""
        from ...services.success_marker_service import SuccessMarkerService, create_processing_summary, create_validation_summary
        
        # Create standardized processing summary
        processing_summary = create_processing_summary(
            total_items=self.processing_summary["total_months"],
            processed_items=self.processing_summary["processed_months"],
            failed_items=self.processing_summary["failed_months"],
            input_cost=self.processing_summary["total_input_cost"],
            output_cost=self.processing_summary["total_output_cost"],
            providers_processed=list(self.processing_summary["providers_processed"]),
            months_processed=self.processing_summary["months_processed"]
        )
        
        # Create validation summary (K8s has built-in validation)
        validation_summary = create_validation_summary(
            validation_performed=True,
            validation_passed=True,  # Only written if validation passed
            total_validations=7,  # Number of validation checks in _validate_k8s_processing_result
            failed_validations=0,
            validation_details={
                "workload_minimum_resources_enforced": True,
                "cost_conservation_validated": True,
                "topology_validation_passed": True
            }
        )
        
        # Use standardized success marker service
        success_marker_service = SuccessMarkerService(self.data_service.s3_service)
        success = success_marker_service.create_success_marker(
            dataset_name=self.config.dataset_name,
            stage="k8s",
            processing_summary=processing_summary,
            config=self.config.to_dict(),
            validation_summary=validation_summary
        )
        
        if success:
            self.console.print("✅ [green]SUCCESS marker written[/green]")
        else:
            self.console.print("⚠️  [yellow]Failed to write SUCCESS marker[/yellow]")
    
    def _display_processing_summary(self, failed_months: List[Dict[str, Any]]) -> None:
        """Display final processing summary."""
        
        self.console.print("\n📊 [bold cyan]Processing Summary[/bold cyan]")
        self.console.print(f"• Total months: {self.processing_summary['total_months']}")
        self.console.print(f"• Processed successfully: {self.processing_summary['processed_months']}")
        self.console.print(f"• Failed: {self.processing_summary['failed_months']}")
        self.console.print(f"• Providers: {', '.join(self.processing_summary['providers_processed'])}")
        self.console.print(f"• Total input cost: ${self.processing_summary['total_input_cost']:,.2f}")
        self.console.print(f"• Total output cost: ${self.processing_summary['total_output_cost']:,.2f}")
        
        cost_difference = abs(self.processing_summary['total_input_cost'] - self.processing_summary['total_output_cost'])
        # Only show cost conservation if we actually processed data with costs
        if self.processing_summary['processed_months'] > 0 and self.processing_summary['total_input_cost'] > 0:
            cost_tolerance = min(self.processing_summary['total_input_cost'] * 0.001, 50000.0)
            if cost_difference < cost_tolerance:
                self.console.print("✅ [green]Cost conservation validated[/green]")
            else:
                self.console.print(f"⚠️  [yellow]Cost difference: ${cost_difference:,.2f} (tolerance: ${cost_tolerance:,.2f})[/yellow]")
        
        if failed_months:
            self.console.print("\n❌ [red]Failed Months:[/red]")
            for failure in failed_months:
                self.console.print(f"• {failure['provider']} {failure['month']}: {failure['error']}")
    
    def _validate_k8s_processing_result(self, input_df: pl.DataFrame, output_df: pl.DataFrame, provider: str, month: str) -> Dict[str, Any]:
        """
        Validate K8s processing results.
        
        Args:
            input_df: Original input DataFrame
            output_df: Processed output DataFrame
            provider: Provider name
            month: Month string
            
        Returns:
            Validation report dictionary
        """
        validation_report = {
            "validation_passed": True,
            "provider": provider,
            "month": month,
            "failures": []
        }
        
        # Get counts
        input_rows = len(input_df)
        output_rows = len(output_df)
        
        # Filter K8s rows (those with K8s metadata)
        k8s_rows = output_df.filter(pl.col("k8s_cluster").is_not_null())
        k8s_row_count = len(k8s_rows)
        
        # Get K8s topology counts
        k8s_cluster_count = k8s_rows["k8s_cluster"].n_unique() if k8s_row_count > 0 else 0
        k8s_namespace_count = k8s_rows["k8s_namespace"].n_unique() if k8s_row_count > 0 else 0
        k8s_workload_count = k8s_rows["k8s_workload"].n_unique() if k8s_row_count > 0 else 0
        
        # Cost conservation check using ColumnMapper
        input_cost_column = column_mapper.get_column_name(input_df, "Cost")
        output_cost_column = column_mapper.get_column_name(output_df, "Cost")
        
        if not input_cost_column or not output_cost_column:
            raise ValueError("Cost column not found in input or output DataFrame")
        
        input_cost = float(input_df.get_column(input_cost_column).sum())
        output_cost = float(output_df.get_column(output_cost_column).sum())
        cost_difference = abs(input_cost - output_cost)
        
        # Store metrics in report
        validation_report.update({
            "input_rows": input_rows,
            "output_rows": output_rows,
            "k8s_row_count": k8s_row_count,
            "k8s_cluster_count": k8s_cluster_count,
            "k8s_namespace_count": k8s_namespace_count,
            "k8s_workload_count": k8s_workload_count,
            "input_cost": input_cost,
            "output_cost": output_cost,
            "cost_difference": cost_difference
        })
        
        # Validation 1: Row expansion occurred (dual-constraint algorithm produces ~4x expansion for K8s resources)
        if output_rows <= input_rows:
            validation_report["failures"].append(
                f"No row expansion: output rows ({output_rows}) <= input rows ({input_rows})"
            )
        else:
            # Calculate expansion factor for K8s resources
            k8s_expansion = (output_rows - input_rows) / k8s_row_count if k8s_row_count > 0 else 0
            expected_expansion = 3.0  # Each K8s resource should create ~4 total records (4x - 1 original = 3x net expansion)
            
            # Log expansion details (informational)
            validation_report["expansion_factor"] = round(k8s_expansion + 1, 1) if k8s_row_count > 0 else 0
            validation_report["expected_expansion"] = 4.0
            
            # Don't fail on expansion factor - different resource counts can produce different ratios
        
        # Validation 2: K8s row count > 0 (some resources got K8s allocation)
        if k8s_row_count == 0:
            validation_report["failures"].append(
                f"No K8s allocation: K8s row count is {k8s_row_count}"
            )
        
        # Validation 3: K8s cluster count > 0 (topology generated clusters)
        if k8s_cluster_count == 0:
            validation_report["failures"].append(
                f"No K8s clusters: cluster count is {k8s_cluster_count}"
            )
        
        # Validation 4: K8s namespace count > 0 (topology generated namespaces)
        if k8s_namespace_count == 0:
            validation_report["failures"].append(
                f"No K8s namespaces: namespace count is {k8s_namespace_count}"
            )
        
        # Validation 5: K8s workload count > 0 (topology generated workloads)
        if k8s_workload_count == 0:
            validation_report["failures"].append(
                f"No K8s workloads: workload count is {k8s_workload_count}"
            )
        
        # Validation 6: Cost conservation with tolerance for large datasets
        # Allow up to 0.1% difference or $50,000, whichever is smaller, to handle floating point precision
        cost_tolerance = min(input_cost * 0.001, 50000.0)  # 0.1% or $50K cap
        if cost_difference > cost_tolerance:
            validation_report["failures"].append(
                f"Cost conservation violated: difference ${cost_difference:.2f} (input: ${input_cost:.2f}, output: ${output_cost:.2f}, tolerance: ${cost_tolerance:.2f})"
            )
        
        # Validation 7: Workload minimum resource requirements
        if k8s_row_count > 0:
            workload_resource_violations = self._validate_workload_minimum_resources(k8s_rows)
            if workload_resource_violations:
                violation_count = len(workload_resource_violations)
                validation_report["failures"].append(
                    f"Workload minimum resource violations: {violation_count} workloads have < {self.config.min_resources_per_workload} resources"
                )
                validation_report["workload_violations"] = workload_resource_violations[:10]  # Store first 10 for reporting
        
        # Set overall validation status
        validation_report["validation_passed"] = len(validation_report["failures"]) == 0
        
        if validation_report["validation_passed"]:
            self.logger.info(
                "K8s validation passed",
                provider=provider,
                month=month,
                input_rows=input_rows,
                output_rows=output_rows,
                k8s_row_count=k8s_row_count,
                k8s_cluster_count=k8s_cluster_count,
                k8s_namespace_count=k8s_namespace_count,
                k8s_workload_count=k8s_workload_count,
                cost_difference=cost_difference
            )
        else:
            self.logger.error(
                "K8s validation failed",
                provider=provider,
                month=month,
                failures=validation_report["failures"]
            )
        
        return validation_report
    
    def _validate_workload_minimum_resources(self, k8s_rows: pl.DataFrame) -> List[Dict[str, Any]]:
        """
        Validate that all workloads meet minimum resource requirements.
        
        Args:
            k8s_rows: DataFrame with K8s allocated rows
            
        Returns:
            List of violations (empty if all workloads meet requirements)
        """
        # Group by workload and count unique resources
        workload_resource_counts = (
            k8s_rows
            .group_by("k8s_workload")
            .agg(pl.col("resource_id").n_unique().alias("resource_count"))
        )
        
        # Find workloads with insufficient resources
        violations = []
        for row in workload_resource_counts.iter_rows(named=True):
            workload = row["k8s_workload"]
            resource_count = row["resource_count"]
            
            if resource_count < self.config.min_resources_per_workload:
                violations.append({
                    "workload": workload,
                    "resource_count": resource_count,
                    "minimum_required": self.config.min_resources_per_workload
                })
        
        return violations
    
    def _display_validation_failures(self, validation_report: Dict[str, Any], provider: str, month: str) -> None:
        """
        Display validation failures to console.
        
        Args:
            validation_report: Validation report from _validate_k8s_processing_result
            provider: Provider name
            month: Month string
        """
        self.console.print(f"\n❌ [red]K8s Validation Failed for {provider} {month}:[/red]")
        
        # Display metrics
        self.console.print("📊 [yellow]Processing Metrics:[/yellow]")
        self.console.print(f"  • Input rows: {validation_report['input_rows']:,}")
        self.console.print(f"  • Output rows: {validation_report['output_rows']:,}")
        self.console.print(f"  • K8s row count: {validation_report['k8s_row_count']:,}")
        self.console.print(f"  • K8s cluster count: {validation_report['k8s_cluster_count']:,}")
        self.console.print(f"  • K8s namespace count: {validation_report['k8s_namespace_count']:,}")
        self.console.print(f"  • K8s workload count: {validation_report['k8s_workload_count']:,}")
        self.console.print(f"  • Cost difference: ${validation_report['cost_difference']:.2f}")
        
        # Display failures
        self.console.print("⚠️  [yellow]Validation Failures:[/yellow]")
        for failure in validation_report["failures"]:
            self.console.print(f"  • {failure}")
        
        # Display workload violation details if present
        if "workload_violations" in validation_report and validation_report["workload_violations"]:
            self.console.print("\n🔍 [yellow]Sample Workload Resource Violations:[/yellow]")
            for violation in validation_report["workload_violations"]:
                self.console.print(
                    f"  • Workload '{violation['workload']}': "
                    f"{violation['resource_count']} resources "
                    f"(need {violation['minimum_required']})"
                )
        
        self.console.print()
    
    def _validate_provider_resource_requirements(self, provider: str, month: str) -> bool:
        """
        Validate that a provider has sufficient compute resources for reference implementation topology.
        
        Args:
            provider: Provider name (AWS/Azure/GCP)
            month: Month string (YYYY-MM)
            
        Returns:
            True if sufficient resources, False otherwise
        """
        self.console.print(f"🔍 [cyan]Validating resource requirements for {provider}[/cyan]")
        
        try:
            # Download month data for validation
            month_data = self.data_service.download_month_data(
                self.config.dataset_name, provider, month
            )
            
            if month_data is None or month_data.is_empty():
                self.validation_failures.append({
                    "provider": provider,
                    "month": month,
                    "error": "No data available",
                    "eligible_resources": 0,
                    "required_resources": 350
                })
                # Note: Do not track failed validation
                return False
            
            # Count unique eligible compute resources (not total rows)
            compute_filter = ComputeFilterService(self.config)
            compute_mask = compute_filter.create_compute_mask(month_data)
            eligible_compute_df = month_data.filter(compute_mask)
            
            resource_id_col = column_mapper.get_column_name(eligible_compute_df, "ResourceID")
            eligible_resources = eligible_compute_df[resource_id_col].n_unique() if not eligible_compute_df.is_empty() else 0
            
            # Calculate K8s topology requirements
            total_namespaces = 4 + NAMESPACES_PER_CLUSTER  # 4 default + configured additional
            total_workloads = (
                CLUSTERS_COUNT * 
                total_namespaces * 
                WORKLOADS_PER_NAMESPACE
            )  # 5 × 7 × 2 = 70 workloads
            
            # Dual-constraint algorithm requires minimum 53 resources (fail-fast threshold)
            from ..config import MINIMUM_RESOURCES_REQUIRED
            required_total_eligible_resources = MINIMUM_RESOURCES_REQUIRED
            
            if eligible_resources < required_total_eligible_resources:
                self.validation_failures.append({
                    "provider": provider,
                    "month": month,
                    "eligible_resources": eligible_resources,
                    "required_resources": required_total_eligible_resources,
                    "clusters": CLUSTERS_COUNT,
                    "workloads": total_workloads,
                    "minimum_required": MINIMUM_RESOURCES_REQUIRED
                })
                self.console.print(f"❌ [red]{provider}: {eligible_resources} eligible resources (need {required_total_eligible_resources})[/red]")
                return False
            else:
                self.console.print(f"✅ [green]{provider}: {eligible_resources} eligible resources (sufficient)[/green]")
                return True
                
        except Exception as e:
            self.validation_failures.append({
                "provider": provider,
                "month": month,
                "error": f"Validation error: {str(e)}",
                "eligible_resources": 0,
                "required_resources": 350
            })
            # Note: Do not track failed validation
            self.logger.error(
                "Resource validation failed",
                provider=provider,
                month=month,
                error=str(e)
            )
            return False
    
    def _display_comprehensive_resource_validation_failure(self) -> None:
        """Display comprehensive resource validation failure message and stop processing."""
        
        self.console.print("\n❌ [red bold]K8S RESOURCE VALIDATION FAILED[/red bold]\n")
        
        # Calculate K8s topology requirements from constants
        total_namespaces = 4 + NAMESPACES_PER_CLUSTER
        total_workloads = CLUSTERS_COUNT * total_namespaces * WORKLOADS_PER_NAMESPACE
        min_resources_for_k8s = total_workloads * MIN_RESOURCES_PER_WORKLOAD
        from ..config import MINIMUM_RESOURCES_REQUIRED
        required_total_eligible_resources = MINIMUM_RESOURCES_REQUIRED
        
        self.console.print("📊 [yellow]K8s Dual-Constraint Requirements:[/yellow]")
        self.console.print(f"  • Target topology: {CLUSTERS_COUNT} clusters × {total_namespaces} namespaces × {WORKLOADS_PER_NAMESPACE} workloads = {total_workloads} workloads")
        self.console.print(f"  • Dual constraints: Each resource → 4 workloads, Each workload → 2-5 resources")
        self.console.print(f"  • Optimal resources: ~{total_workloads * 3 // 4} resources for {total_workloads} workloads (3:4 ratio)")
        self.console.print(f"  • **Minimum {required_total_eligible_resources} eligible resources required for fail-fast**")
        self.console.print("")
        
        self.console.print("📋 [yellow]Current Resource Count:[/yellow]")
        for failure in self.validation_failures:
            if "eligible_resources" in failure:
                percentage = (failure["eligible_resources"] / failure["required_resources"]) * 100
                self.console.print(f"  • {failure['provider']}: {failure['eligible_resources']} eligible resources ({percentage:.1f}% of required)")
            else:
                self.console.print(f"  • {failure['provider']}: {failure['error']}")
        self.console.print("")
        
        self.console.print("🔍 [yellow]What makes a resource eligible for K8s allocation:[/yellow]")
        self.console.print("  • AWS: Service = \"AmazonEC2\" AND ResourceType = \"instance\"")
        self.console.print("  • Azure: Service = \"microsoft.compute\" AND ResourceType = \"virtualmachines\"")
        self.console.print("  • GCP: Service = \"Compute Engine\" AND ResourceType = \"instances\"")
        self.console.print("  • Must have valid ResourceID and Cost columns")
        self.console.print("")
        
        self.console.print("💡 [yellow]To fix this issue:[/yellow]")
        self.console.print("  1. Adjust your generate stage configuration to create more compute resources")
        self.console.print("  2. Increase EC2 instances (AWS), virtual machines (Azure), or Compute Engine instances (GCP)")
        self.console.print("  3. Re-run: czbillgen generate run --config-path your-config.yaml")
        self.console.print(f"  4. Target: {required_total_eligible_resources}+ eligible compute resources per provider")
        self.console.print("")
        
        self.console.print("🛑 [red bold]K8s processing stopped. Please regenerate data with sufficient compute resources.[/red bold]")