"""Tabular reporter for biosample enrichment metrics.

Generates CSV files and summary statistics from evaluation results.
"""

from pathlib import Path
from typing import Any

import pandas as pd

from biosample_enricher.logging_config import get_logger
from biosample_enricher.paths import get_data_dir

logger = get_logger(__name__)


class MetricsReporter:
    """Generates tabular reports from evaluation results."""

    def __init__(self, output_dir: Path | None = None):
        """Initialize reporter with output directory.

        Args:
            output_dir: Directory for output files
        """
        self.output_dir = output_dir or (get_data_dir() / "metrics")
        self.output_dir.mkdir(parents=True, exist_ok=True)

    def generate_summary_table(
        self, evaluations: list[dict[str, Any]], exclude_host_associated: bool = False
    ) -> pd.DataFrame:
        """Generate overall coverage summary table.

        Args:
            evaluations: List of evaluation results
            exclude_host_associated: Whether to exclude host-associated samples

        Returns:
            DataFrame with coverage summary by source and data type
        """
        # Optionally filter out host-associated samples
        if exclude_host_associated:
            evaluations = [
                e for e in evaluations if not e.get("is_host_associated", False)
            ]
            logger.info(
                f"Excluding host-associated samples, {len(evaluations)} samples remaining"
            )

        # Separate by source
        nmdc_evals = [e for e in evaluations if e.get("source") == "nmdc"]
        gold_evals = [e for e in evaluations if e.get("source") == "gold"]

        # Calculate metrics for each data type
        metrics = []

        # Elevation metrics
        for source_name, source_evals in [("NMDC", nmdc_evals), ("GOLD", gold_evals)]:
            if not source_evals:
                continue

            # Elevation coverage
            elev_before = sum(
                1 for e in source_evals if e.get("elevation", {}).get("before", False)
            )
            elev_after = sum(
                1 for e in source_evals if e.get("elevation", {}).get("after", False)
            )
            total = len(source_evals)

            metrics.append(
                {
                    "source": source_name,
                    "data_type": "Elevation",
                    "samples": total,
                    "before": round(100 * elev_before / total, 1) if total > 0 else 0,
                    "after": round(100 * elev_after / total, 1) if total > 0 else 0,
                    "improvement": round(100 * (elev_after - elev_before) / total, 1)
                    if total > 0
                    else 0,
                }
            )

            # Place name coverage (overall)
            place_before = sum(
                1 for e in source_evals if e.get("place_name", {}).get("before", False)
            )
            place_after = sum(
                1 for e in source_evals if e.get("place_name", {}).get("after", False)
            )

            metrics.append(
                {
                    "source": source_name,
                    "data_type": "Place Name",
                    "samples": total,
                    "before": round(100 * place_before / total, 1) if total > 0 else 0,
                    "after": round(100 * place_after / total, 1) if total > 0 else 0,
                    "improvement": round(100 * (place_after - place_before) / total, 1)
                    if total > 0
                    else 0,
                }
            )

            # Place name components
            for component in ["country", "state", "locality"]:
                comp_before = sum(
                    1
                    for e in source_evals
                    if e.get("place_name", {})
                    .get("component_coverage", {})
                    .get(component, {})
                    .get("before", False)
                )
                comp_after = sum(
                    1
                    for e in source_evals
                    if e.get("place_name", {})
                    .get("component_coverage", {})
                    .get(component, {})
                    .get("after", False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"Place Name - {component.title()}",
                        "samples": total,
                        "before": round(100 * comp_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * comp_after / total, 1) if total > 0 else 0,
                        "improvement": round(
                            100 * (comp_after - comp_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # Weather coverage (overall)
            weather_before_count = sum(
                e.get("weather", {}).get("before_count", 0) for e in source_evals
            )
            weather_after_count = sum(
                e.get("weather", {}).get("after_count", 0) for e in source_evals
            )
            weather_total_possible = sum(
                e.get("weather", {}).get("total_possible", 7) for e in source_evals
            )

            if weather_total_possible > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "Weather",
                        "samples": total,
                        "before": round(
                            100 * weather_before_count / weather_total_possible, 1
                        ),
                        "after": round(
                            100 * weather_after_count / weather_total_possible, 1
                        ),
                        "improvement": round(
                            100
                            * (weather_after_count - weather_before_count)
                            / weather_total_possible,
                            1,
                        ),
                    }
                )

            # Individual weather parameters
            weather_fields = [
                "temperature",
                "wind_speed",
                "wind_direction",
                "humidity",
                "solar_radiation",
                "precipitation",
                "pressure",
                "chlorophyll",
            ]

            for weather_param in weather_fields:
                param_before = sum(
                    1
                    for e in source_evals
                    if e.get("weather", {}).get("before", {}).get(weather_param, False)
                )
                param_after = sum(
                    1
                    for e in source_evals
                    if e.get("weather", {}).get("after", {}).get(weather_param, False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"Weather - {weather_param.replace('_', ' ').title()}",
                        "samples": total,
                        "before": round(100 * param_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * param_after / total, 1)
                        if total > 0
                        else 0,
                        "improvement": round(
                            100 * (param_after - param_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # Marine coverage (overall)
            marine_before_count = sum(
                e.get("marine", {}).get("before_count", 0) for e in source_evals
            )
            marine_after_count = sum(
                e.get("marine", {}).get("after_count", 0) for e in source_evals
            )
            marine_total_possible = sum(
                e.get("marine", {}).get("total_possible", 7) for e in source_evals
            )

            if marine_total_possible > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "Marine",
                        "samples": total,
                        "before": round(
                            100 * marine_before_count / marine_total_possible, 1
                        ),
                        "after": round(
                            100 * marine_after_count / marine_total_possible, 1
                        ),
                        "improvement": round(
                            100
                            * (marine_after_count - marine_before_count)
                            / marine_total_possible,
                            1,
                        ),
                    }
                )

            # Individual marine parameters
            marine_fields = [
                "sea_surface_temperature",
                "bathymetry",
                "chlorophyll_a",
                "salinity",
                "dissolved_oxygen",
                "ph",
                "wave_height",
            ]

            for marine_param in marine_fields:
                param_before = sum(
                    1
                    for e in source_evals
                    if e.get("marine", {}).get("before", {}).get(marine_param, False)
                )
                param_after = sum(
                    1
                    for e in source_evals
                    if e.get("marine", {}).get("after", {}).get(marine_param, False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"Marine - {marine_param.replace('_', ' ').title()}",
                        "samples": total,
                        "before": round(100 * param_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * param_after / total, 1)
                        if total > 0
                        else 0,
                        "improvement": round(
                            100 * (param_after - param_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # Forward geocoding enrichment - Coordinate enrichment (place names to coordinates)
            coordinates_before = sum(
                1
                for e in source_evals
                if e.get("forward_geocoding", {}).get("before_has_coordinates", False)
            )
            coordinates_after = sum(
                1
                for e in source_evals
                if e.get("forward_geocoding", {}).get("after_has_coordinates", False)
            )
            coordinates_improved = sum(
                1
                for e in source_evals
                if e.get("forward_geocoding", {}).get("coordinates_improved", False)
            )

            metrics.append(
                {
                    "source": source_name,
                    "data_type": "Forward Geocoding - Coordinates",
                    "samples": total,
                    "before": round(100 * coordinates_before / total, 1)
                    if total > 0
                    else 0,
                    "after": round(100 * coordinates_after / total, 1)
                    if total > 0
                    else 0,
                    "improvement": round(100 * coordinates_improved / total, 1)
                    if total > 0
                    else 0,
                }
            )

            # Forward geocoding - Additional enriched fields
            additional_before_total = sum(
                e.get("forward_geocoding", {}).get("additional_fields_before", 0)
                for e in source_evals
            )
            additional_after_total = sum(
                e.get("forward_geocoding", {}).get("additional_fields_after", 0)
                for e in source_evals
            )
            additional_improvement_total = sum(
                e.get("forward_geocoding", {}).get("additional_fields_improvement", 0)
                for e in source_evals
            )

            # Average per sample
            if total > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "Forward Geocoding - Additional Fields",
                        "samples": total,
                        "before": round(additional_before_total / total, 1),
                        "after": round(additional_after_total / total, 1),
                        "improvement": round(additional_improvement_total / total, 1),
                    }
                )

            # Soil enrichment - Overall coverage
            soil_before_count = sum(
                e.get("soil", {}).get("before_count", 0) for e in source_evals
            )
            soil_after_count = sum(
                e.get("soil", {}).get("after_count", 0) for e in source_evals
            )
            soil_total_possible = sum(
                e.get("soil", {}).get("total_possible", 7) for e in source_evals
            )

            if soil_total_possible > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "Soil - Overall",
                        "samples": total,
                        "before": round(
                            100 * soil_before_count / soil_total_possible, 1
                        ),
                        "after": round(100 * soil_after_count / soil_total_possible, 1),
                        "improvement": round(
                            100
                            * (soil_after_count - soil_before_count)
                            / soil_total_possible,
                            1,
                        ),
                    }
                )

            # Individual soil parameters
            soil_fields = [
                "soil_type",
                "soil_classification",
                "ph",
                "texture",
                "organic_carbon",
                "bulk_density",
            ]

            for soil_param in soil_fields:
                param_before = sum(
                    1
                    for e in source_evals
                    if e.get("soil", {}).get("before", {}).get(soil_param, False)
                )
                param_after = sum(
                    1
                    for e in source_evals
                    if e.get("soil", {}).get("after", {}).get(soil_param, False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"Soil - {soil_param.replace('_', ' ').title()}",
                        "samples": total,
                        "before": round(100 * param_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * param_after / total, 1)
                        if total > 0
                        else 0,
                        "improvement": round(
                            100 * (param_after - param_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # Land enrichment - Overall coverage
            land_before_count = sum(
                e.get("land", {}).get("before_count", 0) for e in source_evals
            )
            land_after_count = sum(
                e.get("land", {}).get("after_count", 0) for e in source_evals
            )
            land_total_possible = len(source_evals) * 10  # 10 land/vegetation fields

            if land_total_possible > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "Land - Overall",
                        "samples": total,
                        "before": round(
                            100 * land_before_count / land_total_possible, 1
                        ),
                        "after": round(100 * land_after_count / land_total_possible, 1),
                        "improvement": round(
                            100
                            * (land_after_count - land_before_count)
                            / land_total_possible,
                            1,
                        ),
                    }
                )

            # Individual land cover and vegetation parameters
            land_fields = [
                "current_vegetation",
                "land_cover_class",
                "ndvi",
                "evi",
                "lai",
                "land_use",
                "habitat",
                "biome",
            ]

            for land_param in land_fields:
                param_before = sum(
                    1
                    for e in source_evals
                    if e.get("land", {})
                    .get("before_coverage", {})
                    .get(land_param, False)
                )
                param_after = sum(
                    1
                    for e in source_evals
                    if e.get("land", {})
                    .get("after_coverage", {})
                    .get(land_param, False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"Land - {land_param}",
                        "samples": total,
                        "before": round(100 * param_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * param_after / total, 1)
                        if total > 0
                        else 0,
                        "improvement": round(
                            100 * (param_after - param_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # OSM Features enrichment - Overall coverage
            osm_before_count = sum(
                e.get("osm_features", {}).get("before_count", 0) for e in source_evals
            )
            osm_after_count = sum(
                e.get("osm_features", {}).get("after_count", 0) for e in source_evals
            )
            osm_total_possible = sum(
                e.get("osm_features", {}).get("total_possible", 6) for e in source_evals
            )

            if osm_total_possible > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "OSM Features - Overall",
                        "samples": total,
                        "before": round(100 * osm_before_count / osm_total_possible, 1),
                        "after": round(100 * osm_after_count / osm_total_possible, 1),
                        "improvement": round(
                            100
                            * (osm_after_count - osm_before_count)
                            / osm_total_possible,
                            1,
                        ),
                    }
                )

            # Individual OSM feature categories
            osm_feature_types = [
                "natural_features",
                "water_features",
                "transport_features",
                "buildings",
                "amenities",
                "land_use",
            ]

            for feature_type in osm_feature_types:
                type_before = sum(
                    1
                    for e in source_evals
                    if e.get("osm_features", {})
                    .get("before", {})
                    .get(feature_type, False)
                )
                type_after = sum(
                    1
                    for e in source_evals
                    if e.get("osm_features", {})
                    .get("after", {})
                    .get(feature_type, False)
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": f"OSM Features - {feature_type.replace('_', ' ').title()}",
                        "samples": total,
                        "before": round(100 * type_before / total, 1)
                        if total > 0
                        else 0,
                        "after": round(100 * type_after / total, 1) if total > 0 else 0,
                        "improvement": round(
                            100 * (type_after - type_before) / total, 1
                        )
                        if total > 0
                        else 0,
                    }
                )

            # OSM Features - Named features enrichment
            osm_features_found = sum(
                e.get("osm_features", {}).get("features_found", 0) for e in source_evals
            )
            osm_elements_found = sum(
                e.get("osm_features", {}).get("total_elements", 0) for e in source_evals
            )

            if total > 0:
                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "OSM Features - Named Features",
                        "samples": total,
                        "before": 0.0,  # Assume no named features in original data
                        "after": round(osm_features_found / total, 1),
                        "improvement": round(osm_features_found / total, 1),
                    }
                )

                metrics.append(
                    {
                        "source": source_name,
                        "data_type": "OSM Features - Total Elements",
                        "samples": total,
                        "before": 0.0,  # Assume no OSM elements in original data
                        "after": round(osm_elements_found / total, 1),
                        "improvement": round(osm_elements_found / total, 1),
                    }
                )

        df = pd.DataFrame(metrics)

        # Add formatted improvement column
        if not df.empty:
            df["improvement_fmt"] = df["improvement"].apply(
                lambda x: f"+{x}%" if x > 0 else f"{x}%"
            )

        return df

    def generate_regional_table(
        self, evaluations: list[dict[str, Any]]
    ) -> pd.DataFrame:
        """Generate coverage table by geographic region.

        Args:
            evaluations: List of evaluation results

        Returns:
            DataFrame with coverage by region
        """
        regional_data = []

        # Group by source and region
        for source in ["nmdc", "gold"]:
            source_evals = [e for e in evaluations if e.get("source") == source]

            # Group by region
            regions: dict[str, list[dict[str, Any]]] = {}
            for eval_result in source_evals:
                region = eval_result.get("classification", {}).get("region", "Unknown")
                if region not in regions:
                    regions[region] = []
                regions[region].append(eval_result)

            # Calculate metrics for each region
            for region, region_evals in regions.items():
                if not region_evals:
                    continue

                total = len(region_evals)

                # Has coordinates
                has_coords = sum(
                    1
                    for e in region_evals
                    if e.get("classification", {}).get("has_coordinates", False)
                )

                # Elevation
                elev_before = sum(
                    1
                    for e in region_evals
                    if e.get("elevation", {}).get("before", False)
                )
                elev_after = sum(
                    1
                    for e in region_evals
                    if e.get("elevation", {}).get("after", False)
                )

                # Place name
                place_before = sum(
                    1
                    for e in region_evals
                    if e.get("place_name", {}).get("before", False)
                )
                place_after = sum(
                    1
                    for e in region_evals
                    if e.get("place_name", {}).get("after", False)
                )

                regional_data.append(
                    {
                        "source": source.upper(),
                        "region": region,
                        "samples": total,
                        "has_coordinates_%": round(100 * has_coords / total, 1)
                        if total > 0
                        else 0,
                        "elevation_before_%": round(100 * elev_before / total, 1)
                        if total > 0
                        else 0,
                        "elevation_after_%": round(100 * elev_after / total, 1)
                        if total > 0
                        else 0,
                        "place_name_before_%": round(100 * place_before / total, 1)
                        if total > 0
                        else 0,
                        "place_name_after_%": round(100 * place_after / total, 1)
                        if total > 0
                        else 0,
                    }
                )

        return pd.DataFrame(regional_data)

    def generate_detailed_samples(
        self, evaluations: list[dict[str, Any]], limit: int = 100
    ) -> pd.DataFrame:
        """Generate detailed per-sample results.

        Args:
            evaluations: List of evaluation results
            limit: Maximum number of samples to include

        Returns:
            DataFrame with detailed sample-level data
        """
        detailed = []

        for eval_result in evaluations[:limit]:
            detailed.append(
                {
                    "sample_id": eval_result.get("sample_id"),
                    "source": eval_result.get("source"),
                    "region": eval_result.get("classification", {}).get("region"),
                    "is_us": eval_result.get("classification", {}).get(
                        "is_us_territory"
                    ),
                    "is_ocean": eval_result.get("classification", {}).get("is_ocean"),
                    # Elevation
                    "elev_before": eval_result.get("elevation", {}).get("before_value"),
                    "elev_after": eval_result.get("elevation", {}).get("after_value"),
                    "elev_provider": eval_result.get("elevation", {}).get("provider"),
                    # Place name
                    "place_before": eval_result.get("place_name", {}).get(
                        "before_flat"
                    ),
                    "place_after": eval_result.get("place_name", {}).get("after_flat"),
                    "place_providers": ", ".join(
                        eval_result.get("place_name", {}).get("providers", [])
                    ),
                    # Marine
                    "marine_before_count": eval_result.get("marine", {}).get(
                        "before_count", 0
                    ),
                    "marine_after_count": eval_result.get("marine", {}).get(
                        "after_count", 0
                    ),
                    "marine_improvement": eval_result.get("marine", {}).get(
                        "improved", False
                    ),
                    "marine_providers": ", ".join(
                        eval_result.get("marine", {}).get("providers", [])
                    ),
                    "marine_quality": eval_result.get("marine", {}).get("data_quality"),
                    # Weather
                    "weather_before_count": eval_result.get("weather", {}).get(
                        "before_count", 0
                    ),
                    "weather_after_count": eval_result.get("weather", {}).get(
                        "after_count", 0
                    ),
                    "weather_improvement": eval_result.get("weather", {}).get(
                        "improved", False
                    ),
                    "weather_providers": ", ".join(
                        eval_result.get("weather", {}).get("providers", [])
                    ),
                    "weather_quality": eval_result.get("weather", {}).get(
                        "data_quality"
                    ),
                    # Soil
                    "soil_before_count": eval_result.get("soil", {}).get(
                        "before_count", 0
                    ),
                    "soil_after_count": eval_result.get("soil", {}).get(
                        "after_count", 0
                    ),
                    "soil_improvement": eval_result.get("soil", {}).get(
                        "improved", False
                    ),
                    "soil_providers": ", ".join(
                        eval_result.get("soil", {}).get("providers", [])
                    ),
                    "soil_quality": eval_result.get("soil", {}).get("data_quality"),
                    # Land
                    "land_before_count": eval_result.get("land", {}).get(
                        "before_count", 0
                    ),
                    "land_after_count": eval_result.get("land", {}).get(
                        "after_count", 0
                    ),
                    "land_improvement": eval_result.get("land", {}).get(
                        "improvement", 0
                    ),
                    "land_providers": ", ".join(
                        eval_result.get("land", {}).get("providers_used", [])
                    ),
                    "land_quality": eval_result.get("land", {}).get("data_quality"),
                }
            )

        return pd.DataFrame(detailed)

    def save_all_reports(
        self, evaluations: list[dict[str, Any]], prefix: str = "metrics"
    ) -> dict[str, Path]:
        """Save all report tables to CSV files.

        Args:
            evaluations: List of evaluation results
            prefix: Filename prefix

        Returns:
            Dictionary of report names to file paths
        """
        files = {}

        # Summary table
        summary_df = self.generate_summary_table(evaluations)
        summary_path = self.output_dir / f"{prefix}_summary.csv"
        summary_df.to_csv(summary_path, index=False)
        files["summary"] = summary_path
        logger.info(f"Saved summary table to {summary_path}")

        # Regional table
        regional_df = self.generate_regional_table(evaluations)
        regional_path = self.output_dir / f"{prefix}_by_region.csv"
        regional_df.to_csv(regional_path, index=False)
        files["regional"] = regional_path
        logger.info(f"Saved regional table to {regional_path}")

        # Detailed samples
        detailed_df = self.generate_detailed_samples(evaluations)
        detailed_path = self.output_dir / f"{prefix}_samples.csv"
        detailed_df.to_csv(detailed_path, index=False)
        files["samples"] = detailed_path
        logger.info(f"Saved sample details to {detailed_path}")

        # Print summary to console
        self.print_summary(summary_df)

        return files

    def print_summary(self, summary_df: pd.DataFrame) -> None:
        """Print summary table to console.

        Args:
            summary_df: Summary DataFrame
        """
        print("\n" + "=" * 80)
        print("BIOSAMPLE ENRICHMENT COVERAGE METRICS")
        print("=" * 80)

        if summary_df.empty:
            print("No data available")
            return

        # Format for console display
        display_df = summary_df[
            ["source", "data_type", "samples", "before", "after", "improvement_fmt"]
        ].copy()

        display_df.columns = [
            "Source",
            "Data Type",
            "Samples",
            "Before %",
            "After %",
            "Improvement",
        ]

        print("\n" + display_df.to_string(index=False))
        print("=" * 80)

        # Calculate overall statistics
        for source in summary_df["source"].unique():
            source_data = summary_df[summary_df["source"] == source]
            avg_before = source_data["before"].mean()
            avg_after = source_data["after"].mean()
            avg_improvement = source_data["improvement"].mean()

            print(f"\n{source} Overall:")
            print(f"  Average Before: {avg_before:.1f}%")
            print(f"  Average After: {avg_after:.1f}%")
            print(f"  Average Improvement: +{avg_improvement:.1f}%")

        print()
