"""
K-Effective Estimator

Hand-calculation based k-eff estimator using single-parameter limits
and empirical correlations. Provides conservative estimates for
preliminary screening.
"""

import logging
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

logger = logging.getLogger(__name__)


class EstimatorWarning(str, Enum):
    """Warning types for k-eff estimator."""

    APPROXIMATION = "approximation"
    EXTRAPOLATION = "extrapolation"
    LIMITED_DATA = "limited_data"
    GEOMETRY_EFFECT = "geometry_effect"
    INTERACTION = "interaction"


@dataclass
class EstimatorResult:
    """Result from k-eff estimation."""

    k_eff_estimate: float = 0.0
    uncertainty: float = 0.0
    is_subcritical: bool = True
    margin_to_critical: float = 0.0
    method: str = ""
    warnings: list[str] = field(default_factory=list)
    details: dict[str, Any] = field(default_factory=dict)

    @property
    def is_safe(self) -> bool:
        """Check if configuration is safely subcritical (k-eff + uncertainty < 0.95)."""
        return (self.k_eff_estimate + self.uncertainty) < 0.95


class KEffEstimator:
    """
    Conservative k-eff estimator using hand methods.

    Uses single-parameter limits from ANSI/ANS-8.1 and empirical
    correlations to provide quick screening estimates.

    Warning: This is for preliminary screening only. Detailed criticality
    calculations (SCALE/KENO) are required for final safety analysis.

    Example:
        estimator = KEffEstimator()

        result = estimator.estimate(
            material="U-235",
            mass_kg=10.0,
            form="metal",
            geometry="sphere",
            reflection="bare"
        )

        if result.is_safe:
            print("Configuration appears subcritical")
        else:
            print("Detailed analysis required")
    """

    # ANSI/ANS-8.1 Single-Parameter Limits
    SUBCRITICAL_LIMITS: dict[str, dict[str, dict[str, dict[str, float]]]] = {
        "U-235": {
            "metal": {
                "mass_kg": {"unreflected": 20.1, "reflected": 22.8},
                "sphere_radius_cm": {"unreflected": 5.5, "reflected": 6.2},
                "cylinder_diameter_cm": {"unreflected": 7.2, "reflected": 8.4},
                "slab_thickness_cm": {"unreflected": 1.0, "reflected": 1.5},
            },
            "solution": {
                "mass_kg": {"unreflected": 0.78, "reflected": 0.81},
                "concentration_g_per_l": {"unreflected": 11.6, "reflected": 12.1},
                "volume_l": {"unreflected": 5.8, "reflected": 6.3},
            },
        },
        "Pu-239": {
            "metal": {
                "mass_kg": {"unreflected": 5.0, "reflected": 5.5},
                "sphere_radius_cm": {"unreflected": 4.4, "reflected": 4.9},
                "cylinder_diameter_cm": {"unreflected": 5.8, "reflected": 6.5},
            },
            "solution": {
                "mass_kg": {"unreflected": 0.51, "reflected": 0.53},
                "concentration_g_per_l": {"unreflected": 7.3, "reflected": 7.6},
                "volume_l": {"unreflected": 3.4, "reflected": 3.7},
            },
        },
        "U-233": {
            "metal": {
                "mass_kg": {"unreflected": 6.0, "reflected": 6.7},
            },
            "solution": {
                "mass_kg": {"unreflected": 0.55, "reflected": 0.57},
            },
        },
    }

    # Density values for materials (g/cm³)
    MATERIAL_DENSITIES: dict[str, dict[str, float]] = {
        "U-235": {"metal": 19.1, "oxide": 10.97},
        "Pu-239": {"metal": 19.86, "oxide": 11.46},
        "U-233": {"metal": 18.95, "oxide": 10.2},
    }

    def __init__(self) -> None:
        """Initialize the k-eff estimator."""
        logger.info("Initialized K-Eff Estimator")

    def estimate(
        self,
        material: str,
        mass_kg: float | None = None,
        form: str = "metal",
        geometry: str = "sphere",
        reflection: str = "bare",
        enrichment: float = 1.0,
        concentration_g_per_l: float | None = None,
        volume_l: float | None = None,
        **kwargs: Any,
    ) -> EstimatorResult:
        """
        Estimate k-eff for a fissile material configuration.

        Args:
            material: Fissile material (U-235, Pu-239, U-233)
            mass_kg: Mass in kilograms
            form: Physical form (metal, solution, oxide)
            geometry: Geometry type (sphere, cylinder, slab)
            reflection: Reflection condition (bare, water, concrete)
            enrichment: Enrichment fraction for uranium (default 1.0)
            concentration_g_per_l: For solutions, concentration in g/L
            volume_l: For solutions, volume in liters
            **kwargs: Additional parameters

        Returns:
            EstimatorResult with k-eff estimate and warnings
        """
        result = EstimatorResult(method="single_parameter_limit")
        warnings = []

        # Map reflection to category
        reflection_cat = "reflected" if reflection != "bare" else "unreflected"

        # Get material limits
        if material not in self.SUBCRITICAL_LIMITS:
            result.warnings.append(f"No data for material: {material}")
            result.k_eff_estimate = 1.0  # Conservative
            result.uncertainty = 0.5
            return result

        form_limits = self.SUBCRITICAL_LIMITS[material].get(form, {})
        if not form_limits:
            warnings.append(f"No limits for {material} in {form} form")
            result.warnings = warnings
            result.k_eff_estimate = 1.0
            result.uncertainty = 0.5
            return result

        # Estimate based on available parameters
        if form == "solution" and concentration_g_per_l is not None:
            # Solution concentration limit
            limit = form_limits.get("concentration_g_per_l", {}).get(reflection_cat, float("inf"))
            ratio = concentration_g_per_l / limit
            result.k_eff_estimate = self._ratio_to_keff(ratio)
            result.details["parameter"] = "concentration"
            result.details["ratio_to_limit"] = ratio

        elif form == "solution" and volume_l is not None:
            # Solution volume limit
            limit = form_limits.get("volume_l", {}).get(reflection_cat, float("inf"))
            ratio = volume_l / limit
            result.k_eff_estimate = self._ratio_to_keff(ratio)
            result.details["parameter"] = "volume"
            result.details["ratio_to_limit"] = ratio

        elif mass_kg is not None:
            # Mass limit
            mass_limits = form_limits.get("mass_kg", {})
            limit = mass_limits.get(reflection_cat, float("inf"))

            # Adjust for enrichment (simplified)
            if material == "U-235" and enrichment < 1.0:
                # Enrichment factor (conservative approximation)
                limit = limit / (enrichment ** 0.8)
                warnings.append(f"Enrichment adjustment applied ({enrichment:.0%})")

            ratio = mass_kg / limit
            result.k_eff_estimate = self._ratio_to_keff(ratio)
            result.details["parameter"] = "mass"
            result.details["ratio_to_limit"] = ratio
            result.details["subcritical_limit_kg"] = limit

        else:
            warnings.append("No mass, volume, or concentration provided")
            result.k_eff_estimate = 1.0
            result.uncertainty = 0.5

        # Set uncertainty based on method limitations
        result.uncertainty = 0.1  # Conservative for hand methods

        # Determine if subcritical
        result.is_subcritical = result.k_eff_estimate < 1.0
        result.margin_to_critical = 1.0 - (result.k_eff_estimate + result.uncertainty)

        # Add standard warnings
        warnings.append("Hand calculation - verify with detailed Monte Carlo analysis")
        if geometry not in ["sphere", "cylinder"]:
            warnings.append(f"Geometry {geometry} may require correction factors")

        result.warnings = warnings
        return result

    def _ratio_to_keff(self, ratio: float) -> float:
        """
        Convert parameter ratio to k-eff estimate.

        Uses a simplified correlation based on the observation that
        k-eff scales roughly as the square root of mass for subcritical
        systems.

        Args:
            ratio: Parameter value / subcritical limit

        Returns:
            Estimated k-eff
        """
        if ratio >= 1.0:
            # At or above limit - potentially critical
            return 0.9 + 0.1 * (ratio - 0.5)
        elif ratio > 0.5:
            # Approaching limit - use conservative linear estimate
            return 0.5 + 0.4 * (ratio - 0.25) / 0.75
        else:
            # Well below limit
            return ratio * 0.8

    def validate_geometry(
        self,
        geometry: str,
        material: str,
        form: str,
        dimensions: dict[str, float],
    ) -> tuple[bool, list[str]]:
        """
        Validate geometry against single-parameter limits.

        Args:
            geometry: Geometry type
            material: Fissile material
            form: Physical form
            dimensions: Geometry dimensions

        Returns:
            Tuple of (valid, list of issues)
        """
        issues = []

        if material not in self.SUBCRITICAL_LIMITS:
            issues.append(f"Unknown material: {material}")
            return False, issues

        form_limits = self.SUBCRITICAL_LIMITS[material].get(form, {})

        if geometry == "sphere":
            radius = dimensions.get("radius", float("inf"))
            limit_unreflected = form_limits.get("sphere_radius_cm", {}).get("unreflected", float("inf"))
            limit_reflected = form_limits.get("sphere_radius_cm", {}).get("reflected", float("inf"))

            if radius > limit_unreflected:
                issues.append(f"Sphere radius {radius:.1f} cm exceeds unreflected limit {limit_unreflected:.1f} cm")
            if radius > limit_reflected:
                issues.append(f"Sphere radius {radius:.1f} cm exceeds reflected limit {limit_reflected:.1f} cm")

        elif geometry == "cylinder":
            diameter = dimensions.get("diameter", float("inf"))
            limit_unreflected = form_limits.get("cylinder_diameter_cm", {}).get("unreflected", float("inf"))
            limit_reflected = form_limits.get("cylinder_diameter_cm", {}).get("reflected", float("inf"))

            if diameter > limit_unreflected:
                issues.append(f"Cylinder diameter {diameter:.1f} cm exceeds unreflected limit {limit_unreflected:.1f} cm")

        return len(issues) == 0, issues

    def check_margins(
        self,
        result: EstimatorResult,
        required_margin: float = 0.05,
    ) -> tuple[bool, str]:
        """
        Check if k-eff estimate has adequate safety margin.

        Args:
            result: Estimator result
            required_margin: Required margin below critical (default 0.05)

        Returns:
            Tuple of (adequate, message)
        """
        if result.margin_to_critical >= required_margin:
            return True, f"Adequate margin: {result.margin_to_critical:.3f} >= {required_margin}"
        else:
            return False, f"Insufficient margin: {result.margin_to_critical:.3f} < {required_margin}"

    def get_limit(
        self,
        material: str,
        form: str,
        parameter: str,
        reflection: str = "unreflected",
    ) -> float | None:
        """
        Get a specific subcritical limit value.

        Args:
            material: Fissile material
            form: Physical form
            parameter: Limit parameter name
            reflection: Reflection condition

        Returns:
            Limit value or None if not found
        """
        try:
            return self.SUBCRITICAL_LIMITS[material][form][parameter][reflection]
        except KeyError:
            return None
