"""
Configuration analyzer for pre-generation dataset estimation.

Analyzes pipeline configurations to estimate:
1. Peak month line items per provider 
2. Reference match count per provider
3. Resource hour cost per provider

Uses existing services for maximum code reuse and consistency.
"""

import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union

import polars as pl
import typer

from src.cli.data.reference_loader import load_reference_data
from src.generate.core.filter_reference_data import (
    apply_filters,
    validate_filter_structure,
)
from src.services.configuration import ConfigurationService
from src.services.directory import DirectoryService

app = typer.Typer(help="Configuration analysis commands")


class ConfigAnalyzer:
    """
    Configuration analyzer using existing services for maximum code reuse.
    
    Leverages proven components:
    - ConfigurationService: Multi-config loading and validation
    - DirectoryService: Organization and path management  
    - filter_reference_data: Optimized filtering logic
    - reference_loader: S3 data loading with auto-download
    """
    
    def __init__(self):
        self.directory_service = DirectoryService()
        self._ref_data_cache: Optional[pl.DataFrame] = None
    
    def analyze_path(
        self, 
        path: Union[str, Path], 
        job_name: Optional[str] = None
    ) -> Dict:
        """
        Analyze configurations at given path (file or directory).
        
        Args:
            path: Path to config file or directory containing configs
            job_name: Optional job name filter
            
        Returns:
            Analysis results dictionary
        """
        path = Path(path)
        
        if not path.exists():
            raise FileNotFoundError(f"Path not found: {path}")
        
        # Load reference data once for all analyses
        ref_data = self._get_reference_data()
        if ref_data is None:
            raise RuntimeError("Reference data not available. Run 'czbillgen data download' first.")
        
        if path.is_file():
            return self._analyze_single_file(path, ref_data, job_name)
        else:
            return self._analyze_directory(path, ref_data, job_name)
    
    def _get_reference_data(self) -> Optional[pl.DataFrame]:
        """Load reference data with caching."""
        if self._ref_data_cache is None:
            self._ref_data_cache = load_reference_data()
        return self._ref_data_cache
    
    def _analyze_single_file(
        self, 
        config_path: Path, 
        ref_data: pl.DataFrame, 
        job_name: Optional[str]
    ) -> Dict:
        """Analyze single configuration file."""
        # Extract org name from path structure
        org_name = self._extract_org_name_from_path(config_path)
        
        # Load configuration
        try:
            import yaml
            with open(config_path) as f:
                config = yaml.safe_load(f)
        except Exception as e:
            raise RuntimeError(f"Failed to load config {config_path}: {e}")
        
        # Extract jobs from config
        jobs = self._extract_jobs_from_config(config, config_path.name)
        
        # Filter by job name if specified
        if job_name:
            jobs = [job for job in jobs if job.get('name', '').strip() == job_name.strip()]
            if not jobs:
                raise ValueError(f"Job '{job_name}' not found in {config_path}")
        
        # Analyze each job
        job_analyses = []
        for job in jobs:
            analysis = self._analyze_job(job, ref_data, org_name, config_path.name)
            job_analyses.append(analysis)
        
        return {
            'config_source': str(config_path),
            'org_name': org_name,
            'total_jobs': len(job_analyses),
            'job_analyses': job_analyses,
            'filtered_job': job_name
        }
    
    def _analyze_directory(
        self, 
        dir_path: Path, 
        ref_data: pl.DataFrame, 
        job_name: Optional[str]
    ) -> Dict:
        """Analyze directory containing configuration files."""
        # Try to detect if this is an org directory structure
        org_name = self._extract_org_name_from_path(dir_path)
        
        # Look for config files
        config_files = []
        
        # Check for pipeline configs
        if (dir_path / "billing" / "pipeline-configs").exists():
            # Org directory structure
            try:
                config_service = ConfigurationService(dir_path)
                pipeline_config = config_service.load_pipeline_configs(stage_name="generate")
                
                # Extract jobs from loaded config
                jobs = self._extract_jobs_from_pipeline_config(pipeline_config)
                
                # Filter by job name if specified
                if job_name:
                    jobs = [job for job in jobs if job.get('name', '').strip() == job_name.strip()]
                    if not jobs:
                        raise ValueError(f"Job '{job_name}' not found in {dir_path}")
                
                # Analyze jobs
                job_analyses = []
                for job in jobs:
                    analysis = self._analyze_job(job, ref_data, org_name, "pipeline-configs")
                    job_analyses.append(analysis)
                
                return {
                    'config_source': str(dir_path),
                    'org_name': org_name,
                    'total_jobs': len(job_analyses),
                    'job_analyses': job_analyses,
                    'filtered_job': job_name
                }
                
            except Exception as e:
                # Fallback to file discovery
                pass
        
        # Fallback: Look for .yaml and .yml files in directory
        for pattern in ['*.yaml', '*.yml']:
            for yaml_file in dir_path.rglob(pattern):
                config_files.append(yaml_file)
        
        if not config_files:
            raise ValueError(f"No configuration files found in {dir_path}")
        
        # Analyze all found config files
        all_job_analyses = []
        for config_file in config_files:
            try:
                file_analysis = self._analyze_single_file(config_file, ref_data, job_name)
                all_job_analyses.extend(file_analysis['job_analyses'])
            except Exception as e:
                # Skip files silently when job filtering (they just don't contain the job)
                if job_name and "not found in" in str(e):
                    continue
                # Only show warnings for actual errors
                print(f"Warning: Failed to analyze {config_file}: {e}", file=sys.stderr)
                continue
        
        # If job filtering and no jobs found, provide helpful error
        if job_name and not all_job_analyses:
            raise ValueError(f"Job '{job_name}' not found in any configuration files in {dir_path}")
        
        return {
            'config_source': str(dir_path),
            'org_name': org_name,
            'total_jobs': len(all_job_analyses),
            'job_analyses': all_job_analyses,
            'filtered_job': job_name,
            'config_files': [str(f) for f in config_files]
        }
    
    def _analyze_job(self, job: Dict, ref_data: pl.DataFrame, org_name: str, source_file: str = None) -> Dict:
        """
        Analyze a single job configuration.
        
        Returns job analysis with:
        - Job configuration (total_cost, resource_count, reference_match_count, hours)
        - Per-provider peak month analysis (line_items, reference_match_count, resource_hour_cost)
        """
        # Extract job configuration
        job_name = job.get('name', 'Unnamed Job')
        total_cost = float(job.get('total_cost', 0))
        resource_count = int(job.get('resource_count', 0))
        start_date = job.get('start_date')
        end_date = job.get('end_date')
        filters = job.get('filters', [])
        
        if not start_date or not end_date:
            raise ValueError(f"Job '{job_name}': start_date and end_date required")
        
        # Calculate hours in period
        hours = self._calculate_hours_in_period(start_date, end_date)
        
        # Apply filters to reference data
        try:
            validate_filter_structure(filters)
            filtered_groups = apply_filters(ref_data, filters)
        except Exception as e:
            raise RuntimeError(f"Filter application failed for job '{job_name}': {e}")
        
        # Calculate reference match count (unique combinations across all filters)
        # Use the same wildcard expansion logic as init-configs for consistency
        if filtered_groups:
            total_reference_matches = self._count_reference_matches_with_wildcards(filters, ref_data)
        else:
            total_reference_matches = 0
        
        # Calculate per-provider analysis
        provider_analysis = self._calculate_provider_analysis(
            ref_data, filtered_groups, filters
        )
        
        return {
            'job_name': job_name,
            'source_file': source_file,
            'job_config': {
                'total_cost': total_cost,
                'resource_count': resource_count,
                'reference_match_count': total_reference_matches,
                'hours': hours,
                'time_period': f"{start_date} to {end_date}"
            },
            'filters': filters,
            'provider_analysis': provider_analysis
        }
    
    def _calculate_provider_analysis(
        self, 
        ref_data: pl.DataFrame,
        filtered_groups: List[Tuple[pl.DataFrame, Dict]], 
        filters: List[Dict]
    ) -> Dict[str, Dict]:
        """Calculate per-provider peak month analysis using wildcard-aware counting."""
        if not filtered_groups:
            return {}
        
        # Get unique providers from filtered results
        providers = set()
        for filtered_df, filter_cfg in filtered_groups:
            if not filtered_df.is_empty():
                providers.add(filter_cfg.get('cloud_provider'))
        
        # Calculate wildcard-aware reference matches per provider
        # Use the original filters passed to _analyze_job to avoid duplication
        provider_analysis = {}
        for provider in providers:
            # Filter the original filters to this provider
            provider_filters = [
                filter_cfg for filter_cfg in filters 
                if filter_cfg.get('cloud_provider') == provider
            ]
            
            # Count reference matches for this provider using wildcard expansion (deduplicated)
            reference_matches = self._count_reference_matches_with_wildcards(provider_filters, ref_data)
            
            # Calculate metrics using wildcard-aware count
            peak_month_line_items = reference_matches * 744  # 744 hours per month (31 days * 24 hours)
            
            provider_analysis[provider] = {
                'peak_month_line_items': peak_month_line_items,
                'reference_match_count': reference_matches,
                'resource_hour_cost': 0.0  # Will be calculated at job level with proper cost splitting
            }
        
        return provider_analysis
    
    def _calculate_hours_in_period(self, start_date: str, end_date: str) -> int:
        """Calculate hours between start and end date."""
        # Handle both date formats: YYYY-MM-DD and ISO datetime
        if "T" in str(start_date):
            start = datetime.fromisoformat(str(start_date).replace("Z", "+00:00"))
        else:
            start = datetime.strptime(str(start_date), "%Y-%m-%d")

        if "T" in str(end_date):
            end = datetime.fromisoformat(str(end_date).replace("Z", "+00:00"))
        else:
            end = datetime.strptime(str(end_date), "%Y-%m-%d")
        
        return int((end - start).total_seconds() / 3600)
    
    def _count_reference_matches_with_wildcards(self, filters: List[Dict], ref_data: pl.DataFrame) -> int:
        """
        Count reference matches by querying reference data with exact config filters.
        
        Uses data-driven approach instead of wildcard assumptions - queries reference data
        with the exact filters specified in the config to count actual matches.
        This matches the behavior of 'czbillgen data explore combinations'.
        """
        total_ref_matches = 0
        
        for filter_cfg in filters:
            cloud_provider = filter_cfg.get("cloud_provider")
            services = filter_cfg.get("services", [])
            
            for service_cfg in services:
                service_name = service_cfg.get("name")
                usage_families = service_cfg.get("usage_families", [])
                
                # If no usage families configured, count at service level
                if not usage_families:
                    matches = self._query_reference_data(ref_data, cloud_provider, service_name)
                    total_ref_matches += matches
                    continue
                
                # Count matches for each configured usage family
                for usage_family_cfg in usage_families:
                    family_name = usage_family_cfg.get("name")
                    resource_types_cfg = usage_family_cfg.get("resource_types", [])
                    
                    # If no resource types configured, count at usage family level
                    if not resource_types_cfg:
                        matches = self._query_reference_data(ref_data, cloud_provider, service_name, family_name)
                        total_ref_matches += matches
                        continue
                    
                    # Count matches for each configured resource type
                    for resource_type_cfg in resource_types_cfg:
                        resource_type_name = resource_type_cfg.get("name")
                        operations_cfg = resource_type_cfg.get("operations", [])
                        
                        # If no operations configured, count at resource type level
                        # This handles the GCP Compute Engine case properly
                        if not operations_cfg:
                            matches = self._query_reference_data(
                                ref_data, cloud_provider, service_name, family_name, resource_type_name
                            )
                            total_ref_matches += matches
                            continue
                        
                        # Count matches for each configured operation
                        for operation_cfg in operations_cfg:
                            operation_name = operation_cfg.get("name")
                            usage_types_cfg = operation_cfg.get("usage_types", [])
                            
                            # If no usage types configured, count at operation level
                            if not usage_types_cfg:
                                matches = self._query_reference_data(
                                    ref_data, cloud_provider, service_name, family_name, 
                                    resource_type_name, operation_name
                                )
                                total_ref_matches += matches
                                continue
                            
                            # Count matches for specific usage types
                            for usage_type in usage_types_cfg:
                                matches = self._query_reference_data(
                                    ref_data, cloud_provider, service_name, family_name, 
                                    resource_type_name, operation_name, usage_type
                                )
                                total_ref_matches += matches
        
        return total_ref_matches
    
    def _query_reference_data(
        self, 
        ref_data: pl.DataFrame,
        cloud_provider: str,
        service: Optional[str] = None,
        usage_family: Optional[str] = None,
        resource_type: Optional[str] = None,
        operation: Optional[str] = None,
        usage_type: Optional[str] = None
    ) -> int:
        """Query reference data with exact filters and return count of unique combinations.
        
        Counts unique combinations of the 6 key columns only:
        - cloud_provider, service, usage_family, resource_type, operation, usage_type
        
        Ignores other dimensions like instance_type, pricing_unit that create row multiplication.
        """
        # Start with the full dataset
        filtered_df = ref_data
        
        # Apply filters progressively, using exact matching like data explore does
        filter_mapping = {
            "cloud_provider": cloud_provider,
            "service": service,
            "usage_family": usage_family,
            "resource_type": resource_type,
            "operation": operation,
            "usage_type": usage_type
        }
        
        for column, value in filter_mapping.items():
            if value is not None:
                # Use exact matching for config analysis
                filtered_df = filtered_df.filter(pl.col(column) == value)
        
        # Count unique combinations of the 6 key columns only
        # This avoids counting instance_type, pricing_unit, etc. as separate "combinations"
        key_columns = ["cloud_provider", "service", "usage_family", "resource_type", "operation", "usage_type"]
        unique_combinations = filtered_df.select(key_columns).unique()
        
        return unique_combinations.height
    
    def _extract_jobs_from_config(self, config: Dict, filename: str) -> List[Dict]:
        """Extract jobs from standard configuration format."""
        if 'generation_jobs' not in config:
            raise ValueError(f"Invalid config format in {filename}. Expected 'generation_jobs' field. Standard format: https://docs.example.com/config-format")
        
        jobs = config['generation_jobs']
        if not isinstance(jobs, list):
            jobs = [jobs]
        
        # Add default names if missing
        for i, job in enumerate(jobs):
            if 'name' not in job:
                job['name'] = f"{filename} Job {i+1}"
        
        return jobs
    
    def _extract_jobs_from_pipeline_config(self, pipeline_config: Dict) -> List[Dict]:
        """Extract jobs from loaded pipeline configuration (legacy org structure)."""
        # This handles the org directory structure which may use different format
        # For now, check both formats but prefer the standard one
        if 'generation_jobs' in pipeline_config:
            jobs = pipeline_config['generation_jobs']
        elif 'stages' in pipeline_config and 'generate' in pipeline_config['stages']:
            jobs = pipeline_config['stages']['generate']
        else:
            raise ValueError("No jobs found in pipeline configuration. Expected 'generation_jobs' format.")
        
        if not isinstance(jobs, list):
            jobs = [jobs]
        
        return jobs
    
    def _extract_org_name_from_path(self, path: Path) -> str:
        """Extract organization name from path structure."""
        path_parts = path.parts
        
        # Look for "orgs" in path
        try:
            orgs_index = path_parts.index("orgs")
            if orgs_index + 1 < len(path_parts):
                return path_parts[orgs_index + 1]
        except ValueError:
            pass
        
        # Fallback to parent directory name
        if path.is_file():
            return path.parent.name
        else:
            return path.name


@app.command()
def analyze_config(
    path: str = typer.Argument(help="Path to config file or directory containing configs"),
    job: Optional[str] = typer.Option(
        None, "--job", "-j", help="Filter to specific job name"
    ),
) -> None:
    """
    Analyze configuration to estimate line items, reference matches, and costs.
    
    Shows per-provider peak month analysis and job configuration details.
    
    Examples:
        czbillgen analyze config /path/to/config.yaml
        czbillgen analyze config /path/to/org-directory/
        czbillgen analyze config /path/to/config.yaml --job "AWS Production"
    """
    try:
        analyzer = ConfigAnalyzer()
        results = analyzer.analyze_path(path, job)
        
        # Display results using the format we discussed
        _display_analysis_results(results)
        
    except Exception as e:
        print(f"❌ Configuration analysis failed: {e}", file=sys.stderr)
        raise typer.Exit(1)


def _format_number(value: int) -> str:
    """Format numbers with K, M, B suffixes."""
    if value >= 1_000_000_000:
        return f"{value/1_000_000_000:.1f}B"
    elif value >= 1_000_000:
        return f"{value/1_000_000:.1f}M"
    elif value >= 1_000:
        return f"{value/1_000:.0f}K"
    else:
        return f"{value:,}"


def _format_currency(value: float) -> str:
    """Format currency values with K, M, B suffixes."""
    if value >= 1_000_000_000:
        return f"${value/1_000_000_000:.1f}B"
    elif value >= 1_000_000:
        return f"${value/1_000_000:.1f}M"
    elif value >= 1_000:
        return f"${value/1_000:.0f}K"
    elif value >= 1:
        return f"${value:.2f}"
    else:
        return f"${value:.4f}"


def _display_filters(filters: List[Dict], indent: str = "") -> None:
    """Display filter configuration with complete hierarchical structure."""
    for filter_cfg in filters:
        print(f"{indent}- cloud_provider: {filter_cfg['cloud_provider']}")
        
        # Show variance and anomaly profiles if present
        if 'variance_profile' in filter_cfg:
            print(f"{indent}  variance_profile: {filter_cfg['variance_profile']}")
        if 'anomaly_profile' in filter_cfg:
            print(f"{indent}  anomaly_profile: {filter_cfg['anomaly_profile']}")
        
        # Display services hierarchy
        services = filter_cfg.get('services', [])
        if services:
            print(f"{indent}  services:")
            for service in services:
                print(f"{indent}    - name: {service['name']}")
                
                # Display usage families
                usage_families = service.get('usage_families', [])
                if usage_families:
                    print(f"{indent}      usage_families:")
                    for uf in usage_families:
                        print(f"{indent}        - name: {uf['name']}")
                        
                        # Display resource types
                        resource_types = uf.get('resource_types', [])
                        if resource_types:
                            print(f"{indent}          resource_types:")
                            for rt in resource_types:
                                print(f"{indent}            - name: {rt['name']}")
                                
                                # Display operations
                                operations = rt.get('operations', [])
                                if operations:
                                    print(f"{indent}              operations:")
                                    for op in operations:
                                        print(f"{indent}                - name: {op['name']}")
                                        
                                        # Display usage types
                                        usage_types = op.get('usage_types', [])
                                        if usage_types:
                                            print(f"{indent}                  usage_types:")
                                            for ut in usage_types:
                                                print(f"{indent}                    - {ut}")
                                        else:
                                            print(f"{indent}                  usage_types: []")


def _display_analysis_results(results: Dict) -> None:
    """Display analysis results in clean table format."""
    filtered_job = results.get('filtered_job')
    
    if filtered_job:
        # Job-specific view
        print(f"🎯 Job Analysis: \"{filtered_job}\"")
        print("=" * 50)
        
        if not results['job_analyses']:
            print(f"❌ Job '{filtered_job}' not found")
            return
        
        job = results['job_analyses'][0]  # Should be only one with filtering
        config = job['job_config']
        
        # Phase 7: Remove Hours from Job Configuration display
        print("Job Configuration:")
        print(f"  Total Cost:           ${_format_number(int(config['total_cost']))}")
        print(f"  Resource Count:       {_format_number(config['resource_count'])}")
        print(f"  Reference Match Count: {_format_number(config['reference_match_count'])}")
        print(f"  Time Period:          {config['time_period']}")
        
        print("\n  Filters:")
        _display_filters(job['filters'], indent="    ")
        
        # Create job summaries with proper multi-provider support for single job view
        job_summaries = []
        hours_per_month = 744  # Standardized peak month
        provider_count = len(job['provider_analysis']) if job['provider_analysis'] else 1
        
        for provider, provider_data in job['provider_analysis'].items():
            # Split total cost equally across providers
            provider_total_cost = config['total_cost'] / provider_count if provider_count > 0 else config['total_cost']
            
            # Calculate cost per resource hour based on split cost (using actual job hours, not 744)
            # Formula: Total Cost ÷ (Resource Count × Reference Match Count × Hours)
            job_hours = config['hours']
            provider_ref_matches = provider_data['reference_match_count']
            if config['resource_count'] > 0 and provider_ref_matches > 0 and job_hours > 0:
                split_hourly_cost_per_resource = provider_total_cost / (config['resource_count'] * provider_ref_matches * job_hours)
            else:
                split_hourly_cost_per_resource = 0
            
            job_summaries.append({
                'name': job['job_name'],
                'filename': job.get('source_file', 'Unknown'),
                'provider': provider,
                'peak_month_line_items': provider_data['peak_month_line_items'],
                'reference_match_count': provider_data['reference_match_count'],
                'resource_count': config['resource_count'],
                'hours_per_month': hours_per_month,
                'hourly_cost_per_resource': split_hourly_cost_per_resource,
                'total_cost': provider_total_cost,
                'provider_count': provider_count
            })
        
        # Handle jobs with no provider analysis
        if not job['provider_analysis']:
            job_summaries.append({
                'name': job['job_name'],
                'filename': job.get('source_file', 'Unknown'),
                'provider': 'Unknown',
                'peak_month_line_items': 0,
                'reference_match_count': 0,
                'resource_count': config['resource_count'],
                'hours_per_month': hours_per_month,
                'hourly_cost_per_resource': 0,
                'total_cost': config['total_cost'],
                'provider_count': 1
            })
        
        print("\nPer-Job Peak Month Analysis:")
        _display_job_summary_table(job_summaries)
        
        # Add Per-Provider Summary for single job (if multi-provider)
        if len(job['provider_analysis']) > 1:
            print("\nPer-Provider Summary:")
            provider_summary = {}
            for provider, data in job['provider_analysis'].items():
                # Calculate split cost for multi-provider job
                provider_total_cost = config['total_cost'] / provider_count if provider_count > 0 else config['total_cost']
                provider_summary[provider] = {
                    'peak_month_line_items': data['peak_month_line_items'],
                    'job_count': 1,
                    'total_cost': provider_total_cost
                }
            _display_provider_summary_table(provider_summary)
        
        # Phase 7: Add formula explanation
        print("\nFormula: Peak Month Line Items = Ref Matches × Resource Count × 744 hours")
        if provider_count > 1:
            print("Cost split equally across providers for multi-provider jobs.")
        
    else:
        # Summary view
        print("📊 Configuration Analysis Summary")
        print("=" * 50)
        
        # Aggregate provider data across all jobs
        all_provider_data = {}
        job_summaries = []
        
        for job in results['job_analyses']:
            config = job['job_config']
            
            # Phase 2: Multi-provider job support
            # Create one row per provider for this job
            hours_per_month = 744  # 31 days * 24 hours (standardized)
            
            # Get number of providers for cost splitting (Phase 3 preparation)
            provider_count = len(job['provider_analysis']) if job['provider_analysis'] else 1
            
            # Create a row for each provider in this job
            for provider, provider_data in job['provider_analysis'].items():
                # Calculate per-provider values using provider-specific data
                provider_ref_matches = provider_data['reference_match_count']
                provider_peak_month_line_items = provider_data['peak_month_line_items']
                provider_resource_hour_cost = provider_data['resource_hour_cost']
                
                # Phase 3: Split total cost equally across providers
                provider_total_cost = config['total_cost'] / provider_count if provider_count > 0 else config['total_cost']
                
                # Recalculate cost per resource hour based on split cost
                # Using the original job hours (not 744) since that's how cost per resource hour is defined
                # Formula: Total Cost ÷ (Resource Count × Reference Match Count × Hours)
                job_hours = config['hours']
                if config['resource_count'] > 0 and provider_ref_matches > 0 and job_hours > 0:
                    split_hourly_cost_per_resource = provider_total_cost / (config['resource_count'] * provider_ref_matches * job_hours)
                else:
                    split_hourly_cost_per_resource = 0
                
                job_summaries.append({
                    'name': job['job_name'],
                    'filename': job.get('source_file', 'Unknown'),
                    'provider': provider,
                    'peak_month_line_items': provider_peak_month_line_items,
                    'reference_match_count': provider_ref_matches,
                    'resource_count': config['resource_count'],  # Same resource count applied to each provider
                    'hours_per_month': hours_per_month,  # Always 744
                    'hourly_cost_per_resource': split_hourly_cost_per_resource,  # Based on split cost
                    'total_cost': provider_total_cost,  # Split equally across providers
                    'provider_count': provider_count  # For debugging/validation
                })
            
            # Handle jobs with no provider analysis (edge case)
            if not job['provider_analysis']:
                job_summaries.append({
                    'name': job['job_name'],
                    'filename': job.get('source_file', 'Unknown'),
                    'provider': 'Unknown',
                    'peak_month_line_items': 0,
                    'reference_match_count': 0,
                    'resource_count': config['resource_count'],
                    'hours_per_month': hours_per_month,
                    'hourly_cost_per_resource': 0,
                    'total_cost': config['total_cost'],
                    'provider_count': 1
                })
            
            # Aggregate provider data
            for provider, pdata in job['provider_analysis'].items():
                if provider not in all_provider_data:
                    all_provider_data[provider] = {
                        'peak_month_line_items': 0,
                        'reference_match_count': 0,
                        'resource_hour_cost': []
                    }
                
                # Take maximum line items across jobs for peak month
                all_provider_data[provider]['peak_month_line_items'] = max(
                    all_provider_data[provider]['peak_month_line_items'],
                    pdata['peak_month_line_items']
                )
                all_provider_data[provider]['reference_match_count'] += pdata['reference_match_count']
                if pdata['resource_hour_cost'] > 0:
                    all_provider_data[provider]['resource_hour_cost'].append(pdata['resource_hour_cost'])
        
        # Phase 4: Replace Per-Provider Peak Month Analysis with Per-Job Peak Month Analysis
        print("Per-Job Peak Month Analysis:")
        _display_job_summary_table(job_summaries)
        
        # Phase 5: Add Per-Provider Summary table with job counts and aggregated line items
        print("\nPer-Provider Summary:")
        provider_summary = {}
        for provider, data in all_provider_data.items():
            # Count jobs for this provider
            job_count = sum(1 for job in job_summaries if job['provider'] == provider)
            
            # Aggregate line items across all jobs for this provider
            # This is the sum of all job line items for the provider (total monthly impact)
            total_line_items = sum(job['peak_month_line_items'] for job in job_summaries if job['provider'] == provider)
            
            # Sum total costs for this provider across all jobs
            total_cost = sum(job['total_cost'] for job in job_summaries if job['provider'] == provider)
            
            provider_summary[provider] = {
                'peak_month_line_items': total_line_items,
                'job_count': job_count,
                'total_cost': total_cost
            }
        
        _display_provider_summary_table(provider_summary)
        
        # Phase 7: Add formula explanation to summary view
        print("\nFormula: Peak Month Line Items = Ref Matches × Resource Count × 744 hours")
        print("Provider totals are the sum of all jobs for that provider.")


def _display_provider_table(provider_data: Dict[str, Dict]) -> None:
    """Display provider analysis in table format."""
    if not provider_data:
        print("  No provider data available")
        return
    
    # Calculate column widths based on content (with reasonable maximum)
    providers = list(provider_data.keys())
    max_provider_len = max(len(provider) for provider in providers) if providers else 8
    provider_width = max(min(max_provider_len, 20), 8)  # Between 8-20 chars
    
    # Table header with dynamic width - wider Ref Match and Hour Cost columns
    header_line = "┌" + "─" * (provider_width + 2) + "┬───────────┬─────────────┬───────────────┐"
    middle_line = "├" + "─" * (provider_width + 2) + "┼───────────┼─────────────┼───────────────┤"
    footer_line = "└" + "─" * (provider_width + 2) + "┴───────────┴─────────────┴───────────────┘"
    
    print(header_line)
    print(f"│ {'Provider':<{provider_width}} │ Line Items │ Ref Match   │ Hour Cost     │")
    print(middle_line)
    
    # Provider rows
    for provider in sorted(provider_data.keys()):
        data = provider_data[provider]
        line_items = data.get('peak_month_line_items', 0)
        ref_matches = data.get('reference_match_count', 0)
        hour_cost = data.get('resource_hour_cost', 0)
        
        # Format numbers
        line_items_str = _format_number(line_items) if line_items > 0 else "0"
        ref_matches_str = _format_number(ref_matches) if ref_matches > 0 else "0"
        hour_cost_str = f"{_format_currency(hour_cost)}/hour" if hour_cost > 0 else "N/A"
        
        # Truncate provider name if it's too long
        display_provider = provider if len(provider) <= provider_width else provider[:provider_width-3] + "..."
        
        print(f"│ {display_provider:<{provider_width}} │ {line_items_str:>9} │ {ref_matches_str:>11} │ {hour_cost_str:>13} │")
    
    print(footer_line)


def _calculate_provider_summary_responsive_widths(provider_summary: Dict[str, Dict]) -> tuple[int, int, int, int]:
    """Calculate responsive column widths for provider summary table."""
    import shutil
    
    try:
        terminal_width = shutil.get_terminal_size().columns
    except:
        terminal_width = 120  # Default fallback
    
    # Fixed space for borders, separators, and spacing (11 chars for 4 columns)
    fixed_space = 11
    available_space = max(terminal_width - fixed_space, 50)  # Minimum 50 for basic functionality
    
    # Get actual content lengths
    providers = list(provider_summary.keys())
    max_provider_len = max(len(provider) for provider in providers) if providers else 8
    
    # Calculate max lengths for numeric columns based on data
    max_line_items = max(data.get('peak_month_line_items', 0) for data in provider_summary.values()) if provider_summary else 0
    max_job_count = max(data.get('job_count', 0) for data in provider_summary.values()) if provider_summary else 0
    max_total_cost = max(data.get('total_cost', 0) for data in provider_summary.values()) if provider_summary else 0
    
    line_items_width = max(len(_format_number(max_line_items)), len("Peak Month Line Items"), 12)
    job_count_width = max(len(str(max_job_count)), len("Job Count"), 8)
    total_cost_width = max(len(f"${_format_number(int(max_total_cost))}"), len("Total Cost"), 10)
    
    # Calculate remaining space for provider column
    numeric_space = line_items_width + job_count_width + total_cost_width
    provider_width = max(available_space - numeric_space, 8)  # Minimum 8 for provider
    
    # Ensure provider width doesn't exceed content need + reasonable buffer
    ideal_provider_width = min(max_provider_len + 2, provider_width)
    provider_width = max(ideal_provider_width, 8)
    
    return provider_width, line_items_width, job_count_width, total_cost_width


def _display_provider_summary_table(provider_summary: Dict[str, Dict]) -> None:
    """Display provider summary in table format with dynamic responsive widths."""
    if not provider_summary:
        print("  No provider data available")
        return
    
    # Calculate responsive column widths
    provider_width, line_items_width, job_count_width, total_cost_width = _calculate_provider_summary_responsive_widths(provider_summary)
    
    # Build dynamic table structure
    header_line = "┌" + "─" * (provider_width + 2) + "┬" + "─" * (line_items_width + 2) + "┬" + "─" * (job_count_width + 2) + "┬" + "─" * (total_cost_width + 2) + "┐"
    middle_line = "├" + "─" * (provider_width + 2) + "┼" + "─" * (line_items_width + 2) + "┼" + "─" * (job_count_width + 2) + "┼" + "─" * (total_cost_width + 2) + "┤"
    footer_line = "└" + "─" * (provider_width + 2) + "┴" + "─" * (line_items_width + 2) + "┴" + "─" * (job_count_width + 2) + "┴" + "─" * (total_cost_width + 2) + "┘"
    
    print(header_line)
    print(f"│ {'Provider':<{provider_width}} │ {'Peak Month':<{line_items_width}} │ {'Job Count':<{job_count_width}} │ {'Total Cost':<{total_cost_width}} │")
    print(f"│ {'':<{provider_width}} │ {'Line Items':<{line_items_width}} │ {'':<{job_count_width}} │ {'':<{total_cost_width}} │")
    print(middle_line)
    
    # Provider rows
    for provider in sorted(provider_summary.keys()):
        data = provider_summary[provider]
        line_items = data.get('peak_month_line_items', 0)
        job_count = data.get('job_count', 0)
        total_cost = data.get('total_cost', 0)
        
        # Format numbers
        line_items_str = _format_number(line_items) if line_items > 0 else "0"
        total_cost_str = f"${_format_number(int(total_cost))}" if total_cost > 0 else "$0"
        
        # Use smart truncation for provider names
        display_provider = _smart_truncate(provider, provider_width)
        
        print(f"│ {display_provider:<{provider_width}} │ {line_items_str:>{line_items_width}} │ {job_count:>{job_count_width}} │ {total_cost_str:>{total_cost_width}} │")
    
    print(footer_line)


def _calculate_responsive_widths(job_summaries: List[Dict]) -> tuple[int, int]:
    """Calculate responsive column widths based on terminal size and content."""
    import shutil
    
    try:
        terminal_width = shutil.get_terminal_size().columns
    except:
        terminal_width = 120  # Default fallback
    
    # Fixed space for numeric columns (4 cols × 13 chars) and borders/separators
    fixed_space = (4 * 13) + 7  # 59 characters
    available_space = max(terminal_width - fixed_space, 30)  # Minimum 30 for text columns
    
    # Get actual content lengths
    job_names = [job['name'] for job in job_summaries]
    filenames = [job['filename'] for job in job_summaries]
    max_job_len = max(len(name) for name in job_names) if job_names else 15
    max_file_len = max(len(filename) for filename in filenames) if filenames else 10
    
    # Priority-based allocation: Job names are most important
    # Aim for 70% to job names, 30% to filenames, but adapt based on content
    ideal_job_width = min(max_job_len, int(available_space * 0.7))
    ideal_file_width = min(max_file_len, available_space - ideal_job_width)
    
    # Ensure minimums are met
    job_name_width = max(ideal_job_width, 15)  # Never smaller than 15
    filename_width = max(ideal_file_width, 10)  # Never smaller than 10
    
    # If we exceed available space, prioritize job names
    total_needed = job_name_width + filename_width
    if total_needed > available_space:
        job_name_width = max(available_space - 10, 15)  # Reserve 10 for filename minimum
        filename_width = available_space - job_name_width
    
    return job_name_width, filename_width


def _smart_truncate(text: str, width: int) -> str:
    """Smart truncation that prefers word boundaries when possible."""
    if len(text) <= width:
        return text
    
    if width < 4:
        return text[:width]
    
    # Try to truncate at word boundaries for better readability
    if ' ' in text and width > 10:
        words = text.split(' ')
        truncated = ''
        for word in words:
            test_length = len(truncated + word + '...')
            if test_length <= width:
                truncated += word + ' '
            else:
                break
        if len(truncated.strip()) > 3:  # Only use if we got at least some words
            return truncated.strip() + '...'
    
    # Fallback to character truncation
    return text[:width-3] + '...'


def _display_job_summary_table(job_summaries: List[Dict]) -> None:
    """Display job summary in table format with responsive column widths."""
    if not job_summaries:
        print("  No jobs found")
        return
    
    # Calculate responsive column widths for the new table structure
    job_name_width, filename_width = _calculate_responsive_widths(job_summaries)
    
    # New table with all calculation variables (Provider, Peak Month Line Items, Ref Matches, Resource Count, Hours/Month, Cost/Resource Hour, Total Cost)
    header_line = "┌" + "─" * (job_name_width + 2) + "┬" + "─" * (filename_width + 2) + "┬──────────┬─────────────┬─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐"
    middle_line = "├" + "─" * (job_name_width + 2) + "┼" + "─" * (filename_width + 2) + "┼──────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤"
    footer_line = "└" + "─" * (job_name_width + 2) + "┴" + "─" * (filename_width + 2) + "┴──────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘"
    
    print(header_line)
    print(f"│ {'Job Name':<{job_name_width}} │ {'File Name':<{filename_width}} │ Provider │ Peak Month  │ Ref Matches │ Resource    │ Hours/Month │ Cost/Resource│ Total Cost  │")
    print(f"│ {'':<{job_name_width}} │ {'':<{filename_width}} │          │ Line Items  │             │ Count       │             │ Hour        │             │")
    print(middle_line)
    
    # Job rows
    for job in job_summaries:
        name = job['name']
        filename = job['filename']
        provider = job['provider']
        line_items = job['peak_month_line_items']
        ref_count = job['reference_match_count']
        resource_count = job['resource_count']
        hours_per_month = job['hours_per_month']
        hourly_cost = job['hourly_cost_per_resource']
        total_cost = job['total_cost']
        
        # Format values
        provider_str = provider[:8] if len(provider) <= 8 else provider[:5] + "..."  # Truncate provider to fit 8 chars
        line_items_str = _format_number(line_items) if line_items > 0 else "0"
        ref_count_str = _format_number(ref_count)
        resource_count_str = _format_number(resource_count)
        hours_str = str(hours_per_month)  # Always 744, so no formatting needed
        hourly_cost_str = f"${hourly_cost:.2f}" if hourly_cost > 0 else "N/A"
        total_cost_str = f"${_format_number(int(total_cost))}" if total_cost > 0 else "$0"
        
        # Use smart truncation for better readability
        display_name = _smart_truncate(name, job_name_width)
        display_filename = _smart_truncate(filename, filename_width)
        
        print(f"│ {display_name:<{job_name_width}} │ {display_filename:<{filename_width}} │ {provider_str:>8} │ {line_items_str:>11} │ {ref_count_str:>11} │ {resource_count_str:>11} │ {hours_str:>11} │ {hourly_cost_str:>11} │ {total_cost_str:>11} │")
    
    print(footer_line)


if __name__ == "__main__":
    app()