from enum import Enum
from typing import Dict, Generic, List, Optional, Tuple, Type, TypeVar

from pydantic import BaseModel

TDefect = TypeVar("TDefect", bound=Enum)


class DefectType(str, Enum):
    """
    Enum representing different types of defects.
    """

    structural = "structural"  # structural defects
    operational_and_maintenance = "operational_and_maintenance"  # operational and maintenance defects
    construction = "construction"  # construction defects
    miscellaneous = "miscellaneous"  # miscellaneous defects


class DefectBand(BaseModel):
    """Represents a severity band for a defect."""

    severity: int
    min_value_percent: float = 0.0
    max_value_percent: float = 100.0

    def contains(self, value_percent: float) -> bool:
        return self.min_value_percent <= value_percent <= self.max_value_percent

    @property
    def nassco_band_color(self) -> Tuple[float, float, float]:
        """
        Returns a color based on the severity level.
        """
        if self.severity < 0 or self.severity > 5:
            raise ValueError(f"Severity must be between 0 and 5, got {self.severity}")

        if self.severity == 0:
            return (0.0, 0.0, 0.0)  # Black
        elif self.severity == 1:
            return (0.0, 1.0, 1.0)  # Cyan
        elif self.severity == 2:
            return (0.0, 1.0, 0.0)  # Green
        elif self.severity == 3:
            return (1.0, 1.0, 0.0)  # Yellow
        elif self.severity == 4:
            return (1.0, 0.65, 0.0)  # Orange
        elif self.severity == 5:
            return (1.0, 0.0, 0.0)  # Red

        return (0.0, 0.0, 0.0)  # Default to black

    @property
    def wrc_band_color(self) -> Tuple[float, float, float]:
        """
        Returns a color based on the severity level.
        """
        if self.severity < 0 or self.severity > 5:
            raise ValueError(f"Severity must be between 0 and 5, got {self.severity}")

        if self.severity == 0:
            return (0.0, 0.0, 0.0)  # Black
        elif self.severity == 1:
            return (0.0, 1.0, 0.0)  # Green
        elif self.severity == 2:
            return (0.0, 0.0, 1.0)  # Blue
        elif self.severity == 3:
            return (1.0, 1.0, 0.0)  # Yellow
        elif self.severity == 4:
            return (1.0, 0.0, 1.0)  # Magenta
        elif self.severity == 5:
            return (1.0, 0.0, 0.0)  # Red

        return (0.0, 0.0, 0.0)  # Default to black

    @property
    def nassco_band_color_bgr(self) -> Tuple[int, int, int]:
        """Returns BGR color for OpenCV (0-255 range)."""
        if self.severity < 0 or self.severity > 5:
            raise ValueError(f"Severity must be between 0 and 5, got {self.severity}")

        if self.severity == 0:
            return (0, 0, 0)  # Black
        elif self.severity == 1:
            return (255, 255, 0)  # Cyan
        elif self.severity == 2:
            return (0, 255, 0)  # Green
        elif self.severity == 3:
            return (0, 255, 255)  # Yellow
        elif self.severity == 4:
            return (0, 165, 255)  # Orange
        elif self.severity == 5:
            return (0, 0, 255)  # Red

        return (0, 0, 0)  # Default to black

    @property
    def wrc_band_color_bgr(self) -> Tuple[int, int, int]:
        """Returns BGR color for OpenCV (0-255 range)."""
        if self.severity < 0 or self.severity > 5:
            raise ValueError(f"Severity must be between 0 and 5, got {self.severity}")

        if self.severity == 0:
            return (0, 0, 0)  # Black
        elif self.severity == 1:
            return (0, 255, 0)  # Green
        elif self.severity == 2:
            return (255, 0, 0)  # Blue
        elif self.severity == 3:
            return (0, 255, 255)  # Yellow
        elif self.severity == 4:
            return (255, 0, 255)  # Magenta
        elif self.severity == 5:
            return (0, 0, 255)  # Red

        return (0, 0, 0)  # Default to black

    class Config:
        frozen = True


class DefectDefinition(BaseModel):
    """Represents a complete defect definition with type and severity bands."""

    defect_type: DefectType
    bands: List[DefectBand]

    def from_percent(self, value_percent: float) -> Optional[DefectBand]:
        """Get the appropriate severity band for a given percentage value."""
        for band in self.bands:
            if band.contains(value_percent):
                return band
        return None

    def get_severity(self, value_percent: float) -> Optional[int]:
        """Get the severity level for a given percentage value."""
        band = self.from_percent(value_percent)
        return band.severity if band else None

    class Config:
        frozen = True


class DefectRegistry(Generic[TDefect]):
    def __init__(self, enum_cls: Type[TDefect], mapping: Dict[TDefect, DefectDefinition]):
        self.enum_cls = enum_cls
        self._registry: Dict[TDefect, DefectDefinition] = mapping

    def get(self, defect: TDefect) -> DefectDefinition:
        return self._registry.get(defect)

    def all(self) -> Dict[TDefect, DefectDefinition]:
        return self._registry

    def from_percent(self, defect: TDefect, value_percent: float) -> Optional[DefectBand]:
        defect_def = self.get(defect)
        if defect_def:
            return defect_def.from_percent(value_percent)
        return None

    def get_defect_type(self, defect: TDefect) -> Optional[DefectType]:
        """Get the defect type for a specific defect."""

        defect_def = self.get(defect)
        return defect_def.defect_type if defect_def else None

    def get_defects_by_type(self, defect_type: DefectType) -> Dict[TDefect, DefectDefinition]:
        """Get all defects with a particular defect type."""

        filtered_defects = {defect: defect_def for defect, defect_def in self._registry.items() if defect_def.defect_type == defect_type}
        return filtered_defects

    def to_json(self) -> dict:
        return {
            defect.value: {"defect_type": defect_def.defect_type.value, "bands": [{**band.model_dump(), "color": band.color} for band in defect_def.bands]}
            for defect, defect_def in self._registry.items()
        }

    @classmethod
    def from_config(cls, enum_cls: Type[TDefect], config: Dict[TDefect, DefectDefinition]) -> "DefectRegistry[TDefect]":
        return cls(enum_cls, config)
