"""
Nuclear Criticality Safety Unit Converter

Handles conversions between common units used in nuclear criticality
safety calculations.
"""

import logging
from dataclasses import dataclass
from enum import Enum

logger = logging.getLogger(__name__)


class UnitCategory(str, Enum):
    """Categories of units."""

    MASS = "mass"
    VOLUME = "volume"
    CONCENTRATION = "concentration"
    DENSITY = "density"
    LENGTH = "length"
    ENRICHMENT = "enrichment"
    ACTIVITY = "activity"


@dataclass
class ConversionResult:
    """Result of a unit conversion."""

    value: float
    from_unit: str
    to_unit: str
    category: UnitCategory
    notes: list[str]


class UnitConverter:
    """
    Unit converter for nuclear criticality safety calculations.

    Handles conversions between common units used in NCS work,
    including mass, volume, concentration, and enrichment formats.

    Example:
        converter = UnitConverter()

        # Mass conversion
        result = converter.convert(10.0, "kg", "g")
        print(f"{result.value} g")

        # Concentration conversion
        result = converter.convert(50.0, "g/L", "mg/mL")

        # Enrichment conversion
        result = converter.convert(0.93, "fraction", "percent")
    """

    # Conversion factors to base units (keys are lowercase for case-insensitive lookup)
    # Base units: kg, L, g/L, g/cm³, m, fraction
    CONVERSION_FACTORS: dict[str, dict[str, tuple[float, str]]] = {
        # Mass conversions (base: kg)
        "mass": {
            "kg": (1.0, "kg"),
            "g": (0.001, "kg"),
            "mg": (0.000001, "kg"),
            "lb": (0.453592, "kg"),
            "oz": (0.0283495, "kg"),
        },
        # Volume conversions (base: L)
        "volume": {
            "l": (1.0, "L"),
            "ml": (0.001, "L"),
            "cm3": (0.001, "L"),
            "m3": (1000.0, "L"),
            "gal": (3.78541, "L"),
            "ft3": (28.3168, "L"),
        },
        # Concentration conversions (base: g/L)
        "concentration": {
            "g/l": (1.0, "g/L"),
            "mg/l": (0.001, "g/L"),
            "mg/ml": (1.0, "g/L"),
            "g/ml": (1000.0, "g/L"),
            "kg/l": (1000.0, "g/L"),
            "kg/m3": (0.001, "g/L"),
        },
        # Density conversions (base: g/cm³)
        "density": {
            "g/cm3": (1.0, "g/cm³"),
            "g/cc": (1.0, "g/cm³"),
            "kg/m3": (0.001, "g/cm³"),
            "kg/l": (1.0, "g/cm³"),
            "lb/ft3": (0.016018, "g/cm³"),
        },
        # Length conversions (base: m)
        "length": {
            "m": (1.0, "m"),
            "cm": (0.01, "m"),
            "mm": (0.001, "m"),
            "in": (0.0254, "m"),
            "ft": (0.3048, "m"),
        },
        # Enrichment conversions (base: fraction)
        "enrichment": {
            "fraction": (1.0, "fraction"),
            "percent": (0.01, "fraction"),
            "%": (0.01, "fraction"),
            "wt%": (0.01, "fraction"),
            "atom%": (0.01, "fraction"),
        },
    }

    # Unit category lookup
    UNIT_CATEGORIES: dict[str, UnitCategory] = {}

    def __init__(self) -> None:
        """Initialize the unit converter."""
        # Build unit category lookup
        for category, units in self.CONVERSION_FACTORS.items():
            for unit in units:
                self.UNIT_CATEGORIES[unit.lower()] = UnitCategory(category)
        logger.info("Initialized Unit Converter")

    def convert(
        self,
        value: float,
        from_unit: str,
        to_unit: str,
    ) -> ConversionResult:
        """
        Convert a value between units.

        Args:
            value: Value to convert
            from_unit: Source unit
            to_unit: Target unit

        Returns:
            ConversionResult with converted value
        """
        notes: list[str] = []

        # Normalize unit names
        from_unit_norm = from_unit.lower().replace(" ", "")
        to_unit_norm = to_unit.lower().replace(" ", "")

        # Find categories
        from_cat = self._get_category(from_unit_norm)
        to_cat = self._get_category(to_unit_norm)

        if from_cat is None:
            raise ValueError(f"Unknown unit: {from_unit}")
        if to_cat is None:
            raise ValueError(f"Unknown unit: {to_unit}")
        if from_cat != to_cat:
            raise ValueError(
                f"Cannot convert between {from_cat.value} and {to_cat.value}"
            )

        # Get conversion factors
        factors = self.CONVERSION_FACTORS[from_cat.value]
        from_factor = factors[from_unit_norm][0]
        to_factor = factors[to_unit_norm][0]

        # Convert through base unit
        base_value = value * from_factor
        result_value = base_value / to_factor

        return ConversionResult(
            value=result_value,
            from_unit=from_unit,
            to_unit=to_unit,
            category=from_cat,
            notes=notes,
        )

    def validate_units(self, unit: str) -> tuple[bool, UnitCategory | None]:
        """
        Validate if a unit is recognized.

        Args:
            unit: Unit string to validate

        Returns:
            Tuple of (valid, category)
        """
        cat = self._get_category(unit.lower().replace(" ", ""))
        return cat is not None, cat

    def get_conversion_factor(
        self,
        from_unit: str,
        to_unit: str,
    ) -> float:
        """
        Get the conversion factor between two units.

        Args:
            from_unit: Source unit
            to_unit: Target unit

        Returns:
            Multiplication factor to convert from_unit to to_unit
        """
        result = self.convert(1.0, from_unit, to_unit)
        return result.value

    def _get_category(self, unit: str) -> UnitCategory | None:
        """Get the category for a unit."""
        # Direct lookup
        if unit in self.UNIT_CATEGORIES:
            return self.UNIT_CATEGORIES[unit]

        # Check in conversion factors
        for category, units in self.CONVERSION_FACTORS.items():
            if unit in units:
                return UnitCategory(category)

        return None

    def mass_to_volume(
        self,
        mass: float,
        mass_unit: str,
        density: float,
        density_unit: str,
        volume_unit: str = "L",
    ) -> ConversionResult:
        """
        Convert mass to volume using density.

        Args:
            mass: Mass value
            mass_unit: Unit of mass
            density: Density value
            density_unit: Unit of density
            volume_unit: Desired output unit

        Returns:
            ConversionResult with volume
        """
        # Convert mass to kg
        mass_kg = self.convert(mass, mass_unit, "kg").value

        # Convert density to g/cm³
        density_gcc = self.convert(density, density_unit, "g/cm3").value

        # Calculate volume in cm³
        mass_g = mass_kg * 1000
        volume_cc = mass_g / density_gcc

        # Convert to desired unit
        volume_L = volume_cc / 1000
        result = self.convert(volume_L, "L", volume_unit)

        result.notes.append(f"Calculated from {mass} {mass_unit} at density {density} {density_unit}")
        return result

    def concentration_to_mass(
        self,
        concentration: float,
        conc_unit: str,
        volume: float,
        volume_unit: str,
        mass_unit: str = "kg",
    ) -> ConversionResult:
        """
        Convert concentration and volume to total mass.

        Args:
            concentration: Concentration value
            conc_unit: Unit of concentration
            volume: Volume value
            volume_unit: Unit of volume
            mass_unit: Desired output unit

        Returns:
            ConversionResult with mass
        """
        # Convert concentration to g/L
        conc_g_per_L = self.convert(concentration, conc_unit, "g/L").value

        # Convert volume to L
        volume_L = self.convert(volume, volume_unit, "L").value

        # Calculate mass in grams
        mass_g = conc_g_per_L * volume_L

        # Convert to desired unit
        mass_kg = mass_g / 1000
        result = self.convert(mass_kg, "kg", mass_unit)

        result.notes.append(
            f"Calculated from {concentration} {conc_unit} × {volume} {volume_unit}"
        )
        return result

    def enrichment_format(
        self,
        enrichment: float,
        from_format: str,
        to_format: str,
    ) -> ConversionResult:
        """
        Convert enrichment between formats.

        Args:
            enrichment: Enrichment value
            from_format: Source format (fraction, percent, wt%, atom%)
            to_format: Target format

        Returns:
            ConversionResult with converted enrichment
        """
        return self.convert(enrichment, from_format, to_format)

    def list_units(self, category: UnitCategory | None = None) -> dict[str, list[str]]:
        """
        List available units.

        Args:
            category: Filter by category

        Returns:
            Dictionary of category -> list of units
        """
        result: dict[str, list[str]] = {}
        for cat_name, units in self.CONVERSION_FACTORS.items():
            if category is None or UnitCategory(cat_name) == category:
                result[cat_name] = list(units.keys())
        return result
