"""
Explore reference data with progressive drilldown for writing pipeline configs.
"""

import typer
from pathlib import Path
from typing import Optional
import polars as pl
import yaml
from rich.console import Console
from rich.table import Table

from ..common import BaseCommand, display_summary_table
from .reference_loader import load_reference_data, ensure_reference_data_available
from ...services.environment_service import env_service

console = Console()
app = typer.Typer(help="Explore reference data for pipeline config creation")


class DataExploreCommand(BaseCommand):
    """Explore reference data with progressive drilldown."""
    
    def explore_data(
        self,
        cloud_provider: Optional[str] = None,
        service: Optional[str] = None,
        usage_family: Optional[str] = None,
        resource_type: Optional[str] = None,
        operation: Optional[str] = None,
        usage_type: Optional[str] = None,
        has_pricing: bool = False,
        exact_match: bool = False,
        format_output: str = "table",
        limit: int = 100,
        export_file: Optional[str] = None,
    ):
        """Explore reference data with progressive drilldown for writing pipeline configs.
        
        Uses partial matching by default (case-insensitive) unless exact_match=True.
        """
        try:
            # FAIL FAST: Validate AWS credentials before any work
            env_service.validate_aws_credentials_and_access()
            
            # Load reference data from S3 only
            df = load_reference_data()
            
            if df is None:
                if not ensure_reference_data_available():
                    return
            
            # Apply filters progressively
            filtered_df = self._apply_filters(df, {
                "cloud_provider": cloud_provider,
                "service": service,
                "usage_family": usage_family,
                "resource_type": resource_type,
                "operation": operation,
                "usage_type": usage_type
            }, exact_match)
            
            # Apply pricing filter
            if has_pricing:
                filtered_df = filtered_df.filter(pl.col("has_pricing"))
            
            if filtered_df.is_empty():
                console.print("[yellow]No matching combinations found.[/yellow]")
                return
            
            # Show matched context if filters were applied
            if not exact_match and (cloud_provider or service):
                self._show_matched_context(filtered_df, cloud_provider, service)
            
            # Determine what to show based on filters
            filter_level = self._determine_drill_level({
                "cloud_provider": cloud_provider,
                "service": service, 
                "usage_family": usage_family,
                "resource_type": resource_type,
                "operation": operation,
                "usage_type": usage_type
            })
            
            if format_output == "yaml":
                self._display_yaml_output(filtered_df, filter_level, limit)
            else:
                self._display_table_output(filtered_df, filter_level, limit, has_pricing)
            
            # Export if requested
            if export_file:
                self._export_results(filtered_df, export_file)
                
        except Exception as e:
            self.handle_exception(e, "exploring reference data")
    
    def get_structured_data(
        self,
        cloud_provider: Optional[str] = None,
        service: Optional[str] = None,
        usage_family: Optional[str] = None,
        resource_type: Optional[str] = None,
        operation: Optional[str] = None,
        usage_type: Optional[str] = None,
        has_pricing: bool = False,
        exact_match: bool = False,
        limit: int = 100,
    ) -> pl.DataFrame:
        """Get structured data for programmatic use without console output.
        
        Returns the filtered DataFrame that can be used by other commands.
        """
        # Validate AWS credentials
        env_service.validate_aws_credentials_and_access()
        
        # Load reference data
        df = load_reference_data()
        if df is None:
            ensure_reference_data_available()
            df = load_reference_data()
            if df is None:
                return pl.DataFrame()
        
        # Apply filters
        filtered_df = self._apply_filters(df, {
            "cloud_provider": cloud_provider,
            "service": service,
            "usage_family": usage_family,
            "resource_type": resource_type,
            "operation": operation,
            "usage_type": usage_type
        }, exact_match)
        
        # Apply pricing filter
        if has_pricing:
            filtered_df = filtered_df.filter(pl.col("has_pricing"))
        
        return filtered_df.head(limit) if limit else filtered_df

    def _apply_filters(self, df: pl.DataFrame, filters: dict, exact_match: bool = False) -> pl.DataFrame:
        """Apply progressive filters to dataframe with contains or exact matching."""
        for column, value in filters.items():
            if value is not None:
                if exact_match:
                    df = df.filter(pl.col(column) == value)
                else:
                    # Case-insensitive contains matching, handling nulls
                    df = df.filter(
                        pl.col(column).is_not_null() & 
                        pl.col(column).str.to_lowercase().str.contains(value.lower())
                    )
        return df
    
    def _show_matched_context(self, df: pl.DataFrame, cloud_provider_filter: Optional[str], service_filter: Optional[str]):
        """Show what providers/services were actually matched by partial search."""
        matches = []
        
        if cloud_provider_filter:
            unique_providers = df["cloud_provider"].unique().sort().to_list()
            if len(unique_providers) > 1:
                matches.append(f"🏢 Providers matching '{cloud_provider_filter}': {', '.join(unique_providers)}")
            
        if service_filter:
            unique_services = df["service"].unique().sort().to_list()[:5]  # Limit to first 5
            if len(unique_services) > 1:
                service_list = ', '.join(unique_services)
                if len(df["service"].unique()) > 5:
                    service_list += f" (and {len(df['service'].unique()) - 5} more)"
                matches.append(f"🔧 Services matching '{service_filter}': {service_list}")
        
        if matches:
            console.print("")
            for match in matches:
                console.print(f"[dim]{match}[/dim]")
            console.print("")
    
    def _determine_drill_level(self, filters: dict) -> str:
        """Determine what level to display based on filters applied."""
        if filters["usage_type"] is not None:
            return "usage_types"  # Fully drilled down
        elif filters["operation"] is not None:
            return "usage_types"
        elif filters["resource_type"] is not None:
            return "operations"
        elif filters["usage_family"] is not None:
            return "resource_types"
        elif filters["service"] is not None:
            return "usage_families"
        elif filters["cloud_provider"] is not None:
            return "services"
        else:
            return "providers"
    
    def _display_table_output(self, df: pl.DataFrame, level: str, limit: int, has_pricing: bool):
        """Display results in table format."""
        if level == "providers":
            self._show_providers(df, limit, has_pricing)
        elif level == "services":
            self._show_services(df, limit, has_pricing)
        elif level == "usage_families":
            self._show_usage_families(df, limit, has_pricing)
        elif level == "resource_types":
            self._show_resource_types(df, limit, has_pricing)
        elif level == "operations":
            self._show_operations(df, limit, has_pricing)
        elif level == "usage_types":
            self._show_usage_types(df, limit, has_pricing)
    
    def _show_providers(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show provider-level summary."""
        # Count unique service+usage_family+resource_type+operation+usage_type combinations for each provider and how many have pricing
        df_with_combos = df.with_columns(
            pl.concat_str([
                pl.col("service").fill_null(""),
                pl.col("usage_family").fill_null(""), 
                pl.col("resource_type").fill_null(""),
                pl.col("operation").fill_null(""),
                pl.col("usage_type").fill_null("")
            ], separator="|").alias("combination")
        )
        provider_summary = df_with_combos.group_by("cloud_provider").agg([
            pl.col("combination").n_unique().alias("combinations"),
            pl.col("combination").filter(pl.col("has_pricing")).n_unique().alias("with_pricing")
        ]).sort("combinations", descending=True).head(limit)
        
        provider_summary = provider_summary.with_columns([
            (pl.col("with_pricing") / pl.col("combinations") * 100).round(1).alias("coverage_pct")
        ])
        
        console.print(f"🏢 [bold]Cloud Providers[/bold] (showing up to {limit})")
        console.print(f"💡 [dim]Next: czbillgen data explore combinations --cloud-provider <provider> (e.g., --cloud-provider aws)[/dim]\n")
        
        provider_data = []
        for row in provider_summary.iter_rows(named=True):
            provider_data.append({
                "provider": row["cloud_provider"],
                "combinations": f"{row['combinations']:,}",  # Each provider is 1 filter combination at this level
                "with_usage_data": f"{row['with_pricing']:,} ({row['coverage_pct']:.1f}%)"
            })
        
        display_summary_table(console, "", provider_data, ["provider", "combinations", "with_usage_data"])
    
    def _show_services(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show service-level summary."""
        # Count unique usage_family+resource_type+operation+usage_type combinations for each service and how many have pricing
        df_with_combos = df.with_columns(
            pl.concat_str([
                pl.col("usage_family").fill_null(""), 
                pl.col("resource_type").fill_null(""),
                pl.col("operation").fill_null(""),
                pl.col("usage_type").fill_null("")
            ], separator="|").alias("combination")
        )
        service_summary = df_with_combos.group_by("service").agg([
            pl.col("combination").n_unique().alias("combinations"),
            pl.col("combination").filter(pl.col("has_pricing")).n_unique().alias("with_pricing")
        ]).sort("combinations", descending=True).head(limit)
        
        provider = df["cloud_provider"].unique().to_list()[0] if df.height > 0 else "Unknown"
        console.print(f"🔧 [bold]Services for {provider}[/bold] (showing up to {limit})")
        console.print(f"💡 [dim]Next: czbillgen data explore combinations --cloud-provider {provider} --service <service> (e.g., --service ec2)[/dim]\n")
        
        service_data = []
        for row in service_summary.iter_rows(named=True):
            pricing_indicator = " ✓" if row["with_pricing"] > 0 else ""
            service_data.append({
                "service": f"{row['service']}{pricing_indicator}",
                "combinations": f"{row['combinations']:,}",
                "with_usage_data": f"{row['with_pricing']:,}"
            })
        
        display_summary_table(console, "", service_data, ["service", "combinations", "with_usage_data"])
    
    def _show_usage_families(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show usage family summary with provider and service context."""
        # Count unique resource_type+operation+usage_type combinations for each usage family and how many have pricing
        df_with_combos = df.with_columns(
            pl.concat_str([
                pl.col("resource_type").fill_null(""),
                pl.col("operation").fill_null(""),
                pl.col("usage_type").fill_null("")
            ], separator="|").alias("combination")
        )
        family_summary = df_with_combos.group_by(["cloud_provider", "service", "usage_family"]).agg([
            pl.col("combination").n_unique().alias("combinations"),
            pl.col("combination").filter(pl.col("has_pricing")).n_unique().alias("with_pricing")
        ]).sort("combinations", descending=True).head(limit)
        
        console.print(f"📊 [bold]Usage Families[/bold] (showing up to {limit})")
        console.print(f"💡 [dim]Next: Add --usage-family \"<family>\" to drill down further[/dim]\n")
        
        family_data = []
        for row in family_summary.iter_rows(named=True):
            pricing_indicator = " ✓" if row["with_pricing"] > 0 else ""
            family_data.append({
                "provider": row["cloud_provider"],
                "service": row["service"],
                "usage_family": f"{row['usage_family']}{pricing_indicator}",
                "combinations": f"{row['combinations']:,}",
                "with_usage_data": f"{row['with_pricing']:,}"
            })
        
        display_summary_table(console, "", family_data, ["provider", "service", "usage_family", "combinations", "with_usage_data"])
    
    def _show_resource_types(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show resource type summary with provider, service, and usage family context."""
        # Count unique operation+usage_type combinations for each resource type and how many have pricing
        df_with_combos = df.with_columns(
            pl.concat_str([
                pl.col("operation").fill_null(""),
                pl.col("usage_type").fill_null("")
            ], separator="|").alias("combination")
        )
        type_summary = df_with_combos.group_by(["cloud_provider", "service", "usage_family", "resource_type"]).agg([
            pl.col("combination").n_unique().alias("combinations"),
            pl.col("combination").filter(pl.col("has_pricing")).n_unique().alias("with_pricing")
        ]).sort("combinations", descending=True).head(limit)
        
        console.print(f"🎯 [bold]Resource Types[/bold] (showing up to {limit})")
        console.print(f"💡 [dim]Add --resource-type <type> to drill down further[/dim]\n")
        
        type_data = []
        for row in type_summary.iter_rows(named=True):
            pricing_indicator = " ✓" if row["with_pricing"] > 0 else ""
            type_data.append({
                "provider": row["cloud_provider"],
                "service": row["service"],
                "usage_family": row["usage_family"],
                "resource_type": f"{row['resource_type']}{pricing_indicator}",
                "combinations": f"{row['combinations']:,}",
                "with_usage_data": f"{row['with_pricing']:,}"
            })
        
        display_summary_table(console, "", type_data, ["provider", "service", "usage_family", "resource_type", "combinations", "with_usage_data"])
    
    def _show_operations(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show operation summary with full context."""
        # Count unique usage_types for each operation and how many have pricing
        op_summary = df.group_by(["cloud_provider", "service", "usage_family", "resource_type", "operation"]).agg([
            pl.col("usage_type").n_unique().alias("combinations"),  # Count unique usage_types
            pl.col("usage_type").filter(pl.col("has_pricing")).n_unique().alias("with_pricing")  # Count usage_types with pricing
        ]).sort("combinations", descending=True).head(limit)
        
        console.print(f"⚙️  [bold]Operations[/bold] (showing up to {limit})")
        console.print(f"💡 [dim]Add --operation <operation> to see usage types[/dim]\n")
        
        op_data = []
        for row in op_summary.iter_rows(named=True):
            pricing_indicator = " ✓" if row["with_pricing"] > 0 else ""
            op_data.append({
                "provider": row["cloud_provider"],
                "service": row["service"],
                "usage_family": row["usage_family"],
                "resource_type": row["resource_type"],
                "operation": f"{row['operation']}{pricing_indicator}",
                "combinations": f"{row['combinations']:,}",
                "with_usage_data": f"{row['with_pricing']:,}"
            })
        
        display_summary_table(console, "", op_data, ["provider", "service", "usage_family", "resource_type", "operation", "combinations", "with_usage_data"])
    
    def _show_usage_types(self, df: pl.DataFrame, limit: int, has_pricing: bool):
        """Show usage type details with full context."""
        console.print(f"📋 [bold]Usage Types[/bold] (showing up to {limit})")
        
        # Get unique filter combinations (excluding instance_type and aggregating has_pricing)
        # At usage_types level, each combination should be unique, so combinations count = 1
        usage_summary = df.group_by(["cloud_provider", "service", "usage_family", "resource_type", "operation", "usage_type"]).agg([
            pl.lit(1).alias("combinations"),  # Always 1 at final drill-down level
            pl.col("has_pricing").max().alias("with_pricing")  # True if any row has pricing
        ]).sort("usage_type").head(limit)
        
        type_data = []
        for row in usage_summary.iter_rows(named=True):
            pricing_indicator = " ✓" if row["with_pricing"] > 0 else ""
            
            type_data.append({
                "provider": row["cloud_provider"],
                "service": row["service"],
                "usage_family": row["usage_family"],
                "resource_type": row["resource_type"],
                "operation": row["operation"],
                "usage_type": f"{row['usage_type']}{pricing_indicator}",
                "combinations": f"{row['combinations']:,}",
                "with_usage_data": f"{row['with_pricing']:,}"
            })
        
        display_summary_table(console, "", type_data, ["provider", "service", "usage_family", "resource_type", "operation", "usage_type", "combinations", "with_usage_data"])
    
    def _display_yaml_output(self, df: pl.DataFrame, level: str, limit: int):
        """Display results in YAML format for pipeline configs."""
        console.print(f"📝 [bold]YAML Output for Pipeline Config[/bold]")
        console.print(f"💡 [dim]Copy this structure into your main.yaml[/dim]\n")
        
        if level == "providers":
            # Show provider list
            providers = df["cloud_provider"].unique().sort().to_list()[:limit]
            yaml_output = {"filters": [{"cloud_provider": provider} for provider in providers]}
        
        elif level in ["services", "usage_families", "resource_types", "operations"]:
            # Build hierarchical structure
            yaml_output = self._build_hierarchical_yaml(df, limit)
        
        else:  # usage_types level
            yaml_output = self._build_detailed_yaml(df, limit)
        
        # Display YAML
        yaml_str = yaml.dump(yaml_output, default_flow_style=False, sort_keys=False)
        console.print(f"```yaml\n{yaml_str}```")
    
    def _build_hierarchical_yaml(self, df: pl.DataFrame, limit: int) -> dict:
        """Build hierarchical YAML structure."""
        # Group by service
        services = {}
        
        for row in df.head(limit).iter_rows(named=True):
            service_name = row["service"]
            if service_name not in services:
                services[service_name] = {"usage_families": {}}
            
            family_name = row["usage_family"]
            if family_name not in services[service_name]["usage_families"]:
                services[service_name]["usage_families"][family_name] = {"resource_types": {}}
            
            type_name = row["resource_type"]
            if type_name not in services[service_name]["usage_families"][family_name]["resource_types"]:
                services[service_name]["usage_families"][family_name]["resource_types"][type_name] = {
                    "operations": {}
                }
            
            op_name = row["operation"]
            if op_name not in services[service_name]["usage_families"][family_name]["resource_types"][type_name]["operations"]:
                services[service_name]["usage_families"][family_name]["resource_types"][type_name]["operations"][op_name] = {
                    "usage_types": []
                }
            
            # Add usage type with pricing indicator
            usage_type = row["usage_type"]
            if row["has_pricing"]:
                usage_type += " # ✓ has pricing data"
            
            if usage_type not in services[service_name]["usage_families"][family_name]["resource_types"][type_name]["operations"][op_name]["usage_types"]:
                services[service_name]["usage_families"][family_name]["resource_types"][type_name]["operations"][op_name]["usage_types"].append(usage_type)
        
        # Convert to list format
        services_list = []
        for service_name, service_data in services.items():
            families_list = []
            for family_name, family_data in service_data["usage_families"].items():
                types_list = []
                for type_name, type_data in family_data["resource_types"].items():
                    ops_list = []
                    for op_name, op_data in type_data["operations"].items():
                        ops_list.append({
                            "name": op_name,
                            "usage_types": op_data["usage_types"]
                        })
                    
                    types_list.append({
                        "name": type_name,
                        "operations": ops_list
                    })
                
                families_list.append({
                    "name": family_name,
                    "resource_types": types_list
                })
            
            services_list.append({
                "name": service_name,
                "usage_families": families_list
            })
        
        return {"services": services_list}
    
    def _build_detailed_yaml(self, df: pl.DataFrame, limit: int) -> dict:
        """Build detailed YAML for final level."""
        usage_types = []
        for row in df.head(limit).iter_rows(named=True):
            usage_type = row["usage_type"]
            if row["has_pricing"]:
                usage_type += " # ✓ has pricing data"
            usage_types.append(usage_type)
        
        return {"usage_types": usage_types}
    
    def _export_results(self, df: pl.DataFrame, export_file: str):
        """Export results to parquet."""
        export_path = Path(export_file)
        # Ensure .parquet extension
        if export_path.suffix.lower() not in ['.parquet']:
            export_path = export_path.with_suffix('.parquet')
        df.write_parquet(export_path)
        console.print(f"[green]📄 Results exported to: {export_path}[/green]")


# Create command instance
explore_cmd = DataExploreCommand()


@app.command()
def combinations(
    cloud_provider: Optional[str] = typer.Option(None, "--cloud-provider", help="Filter by cloud provider (partial match)"),
    service: Optional[str] = typer.Option(None, "--service", help="Filter by service (partial match)"),
    usage_family: Optional[str] = typer.Option(None, "--usage-family", help="Filter by usage family (partial match)"),
    resource_type: Optional[str] = typer.Option(None, "--resource-type", help="Filter by resource type (partial match)"),
    operation: Optional[str] = typer.Option(None, "--operation", help="Filter by operation (partial match)"),
    usage_type: Optional[str] = typer.Option(None, "--usage-type", help="Filter by usage type (partial match)"),
    has_pricing: bool = typer.Option(False, "--has-pricing", help="Show only combinations with pricing data"),
    exact_match: bool = typer.Option(False, "--exact", help="Use exact matching instead of partial matching"),
    format_output: str = typer.Option("table", "--format", help="Output format: table or yaml"),
    limit: int = typer.Option(100, "--limit", help="Limit number of results"),
    export: Optional[str] = typer.Option(None, "--export", help="Export results to parquet file"),
):
    """Explore reference data with progressive drilldown (partial matching by default).
    
    Examples:
    - czbillgen data explore combinations --service EC2
    - czbillgen data explore combinations --cloud-provider aws --service s3
    - czbillgen data explore combinations --service "AmazonEC2" --exact
    """
    try:
        # FAIL FAST: Validate AWS credentials before any work
        env_service.validate_aws_credentials_and_access()
        
        explore_cmd.explore_data(
            cloud_provider=cloud_provider,
            service=service,
            usage_family=usage_family,
            resource_type=resource_type,
            operation=operation,
            usage_type=usage_type,
            has_pricing=has_pricing,
            exact_match=exact_match,
            format_output=format_output,
            limit=limit,
            export_file=export,
        )
    except ValueError as e:
        console.print(f"❌ {str(e)}")
        raise typer.Exit(1)