"""Configuration classes for allocation telemetry generation."""

import builtins
from dataclasses import dataclass
from pathlib import Path
from typing import (
    List,
    Literal,
    Optional,
    Union,
)

import yaml

from ..patterns import (
    AnomalyConfig,
    MetricScale,
    SeasonalityConfig,
    TrendConfig,
)

# Optional profiles import - avoid circular dependency
try:
    from ...profiles.converters.telemetry import (
        apply_unified_profiles_to_telemetry_config as apply_profiles_to_config,
    )

    _PROFILES_AVAILABLE = True
except ImportError:
    # Fallback when profiles system is not available
    def apply_profiles_to_config(config_dict):
        return config_dict

    _PROFILES_AVAILABLE = False


@dataclass
class DistributionConfig:
    """Configuration for element usage distribution strategies."""

    strategy: Literal["random", "weighted", "clustered", "pareto"] = "random"
    weights: Optional[List[float]] = None  # For weighted distribution
    cluster_factor: float = (
        0.3  # For clustered distribution (0-1, higher = more clustered)
    )
    pareto_factor: float = 0.8  # For pareto distribution (80/20 rule factor)
    seed: int = 42  # For reproducible distributions


@dataclass
class ElementPatternConfig:
    """Configuration for individual element pattern variations."""

    element_id: str
    variance_factor: Optional[float] = None  # Override global variance
    seasonality: Union[dict, SeasonalityConfig, None] = None
    trend: Union[dict, TrendConfig, None] = None
    anomaly: Union[dict, AnomalyConfig, None] = None
    usage_multiplier: float = 1.0  # Scale element's usage relative to base

    def __post_init__(self):
        """Convert dict values to appropriate types."""
        if self.seasonality and isinstance(self.seasonality, dict):
            self.seasonality = SeasonalityConfig(**self.seasonality)
        if self.trend and isinstance(self.trend, dict):
            self.trend = TrendConfig(**self.trend)
        if self.anomaly and isinstance(self.anomaly, dict):
            self.anomaly = AnomalyConfig(**self.anomaly)


@dataclass
class ElementConfig:
    """Configuration for elements (customers, tenants, etc.) in allocation."""

    type: Literal["generated", "provided"]
    count: Optional[int] = None  # For generated elements
    list: Optional[List[str]] = None  # For provided elements
    naming_pattern: str = "{:03d}"  # For generated elements, e.g. "customer_{:03d}"
    prefix: str = "element"  # For generated elements, e.g. "customer", "tenant"
    distribution: Union[dict, DistributionConfig, None] = None
    patterns: Optional[List[Union[dict, ElementPatternConfig]]] = None

    def __post_init__(self):
        """Validate element configuration."""
        if self.type == "generated" and self.count is None:
            raise ValueError("count is required when type is 'generated'")
        if self.type == "provided" and self.list is None:
            raise ValueError("list is required when type is 'provided'")

        # Convert distribution dict to DistributionConfig
        if self.distribution and isinstance(self.distribution, dict):
            self.distribution = DistributionConfig(**self.distribution)
        elif self.distribution is None:
            self.distribution = DistributionConfig()  # Use defaults

        # Convert pattern dicts to ElementPatternConfig objects
        if self.patterns:
            converted_patterns = []
            for pattern in self.patterns:
                if isinstance(pattern, dict):
                    converted_patterns.append(ElementPatternConfig(**pattern))
                else:
                    converted_patterns.append(pattern)
            self.patterns = converted_patterns


@dataclass
class AllocationConfig:
    """Configuration for individual allocation telemetry generation.

    Supports both detailed configuration and simplified profile-based configuration.
    When profiles are used, the simplified structure is converted to detailed config.
    """

    name: str
    start_date: str
    end_date: str
    elements: Union[dict, ElementConfig, List[str]]  # Simplified: can be just a list of element IDs
    cost_filters: Optional[dict] = None
    frequency: Literal["H", "D"] = "H"  # Hourly or Daily

    # Simplified profile-based fields (alternative to detailed config)
    scale_profile: Optional[str] = None  # "tiny", "small", "medium", "large", "massive"
    distribution_profile: Optional[str] = None  # "balanced", "light_skew", "heavy_skew", etc.
    seasonality_profile: Optional[str] = None  # "minimal", "business_hours", "batch_processing", etc.
    growth_profile: Optional[str] = None  # "stable", "steady_growth", "explosive_growth", etc.
    usage_intensity_profile: Optional[str] = None  # "steady", "bursty", "intermittent", etc.
    anomaly_profile: Optional[str] = None  # "none", "occasional", "frequent"

    # Detailed configuration fields (for backward compatibility and advanced use)
    scale: Union[str, MetricScale, None] = None
    variance_factor: Optional[float] = None
    seasonality: Union[dict, SeasonalityConfig, None] = None
    trend: Union[dict, TrendConfig, None] = None
    anomaly: Union[dict, AnomalyConfig, None] = None

    # Legacy fields (deprecated but supported for backward compatibility)
    shared_resource: Optional[str] = None  # Will be ignored, use cost_filters
    usage_metric: Optional[str] = None  # Will be ignored, always use "unit_value"
    element_dimension: Optional[str] = None  # Will be ignored, always use "element_id"
    seed: int = 42  # For reproducible randomization

    def __post_init__(self):
        """Convert profile/string/dict values to appropriate types."""
        # Convert to dict format for processing
        config_dict = self._to_dict()

        # Apply profiles if any are specified
        if self._has_profiles():
            config_dict = apply_profiles_to_config(config_dict)

        # Extract converted values back to self
        self._apply_converted_config(config_dict)

        # Convert string types to enum/dataclass types
        self._convert_types()

        # Validate configuration
        self._validate()

    def _to_dict(self) -> dict:
        """Convert dataclass to dict for profile processing."""
        result = {}
        for field_name in self.__dataclass_fields__:
            value = getattr(self, field_name)
            if value is not None:
                result[field_name] = value
        return result

    def _has_profiles(self) -> bool:
        """Check if any profile fields are specified."""
        profile_fields = [
            "scale_profile",
            "distribution_profile",
            "seasonality_profile",
            "growth_profile",
            "usage_intensity_profile",
            "anomaly_profile",
        ]
        return any(getattr(self, field) is not None for field in profile_fields)

    def _apply_converted_config(self, config_dict: dict):
        """Apply converted configuration back to dataclass fields."""
        # Handle elements conversion
        if "elements" in config_dict:
            if isinstance(config_dict["elements"], list):
                # Convert simple list to ElementConfig
                self.elements = ElementConfig(
                    type="provided", list=config_dict["elements"]
                )
            elif isinstance(config_dict["elements"], dict):
                self.elements = ElementConfig(**config_dict["elements"])

        # Apply other converted fields
        for field_name in [
            "scale",
            "variance_factor",
            "seasonality",
            "trend",
            "anomaly",
        ]:
            if field_name in config_dict:
                setattr(self, field_name, config_dict[field_name])

    def _convert_types(self):
        """Convert string/dict values to appropriate types."""
        # Convert scale string to MetricScale enum
        if isinstance(self.scale, str):
            self.scale = MetricScale[self.scale.upper()]

        # Convert elements dict to ElementConfig
        if isinstance(self.elements, dict):
            self.elements = ElementConfig(**self.elements)
        elif isinstance(self.elements, list):
            # Simple list of element IDs
            self.elements = ElementConfig(type="provided", list=self.elements)

        # Convert seasonality dict to SeasonalityConfig
        if self.seasonality and isinstance(self.seasonality, dict):
            self.seasonality = SeasonalityConfig(**self.seasonality)

        # Convert trend dict to TrendConfig
        if self.trend and isinstance(self.trend, dict):
            self.trend = TrendConfig(**self.trend)

        # Convert anomaly dict to AnomalyConfig
        if self.anomaly and isinstance(self.anomaly, dict):
            self.anomaly = AnomalyConfig(**self.anomaly)

    def _validate(self):
        """Validate the final configuration."""
        if not self.name:
            raise ValueError("Allocation name is required")

        if not isinstance(self.elements, ElementConfig):
            raise ValueError("Elements must be properly configured")

        if self.scale is None:
            raise ValueError("Scale must be specified (either scale or scale_profile)")

        if self.variance_factor is None:
            # Set default if not specified
            self.variance_factor = 0.2


@dataclass
class AllocationsConfig:
    """Main configuration container for allocation telemetry generation."""

    allocations: List[AllocationConfig]

    @classmethod
    def from_yaml(cls, yaml_path: Path) -> "AllocationsConfig":
        """Load configuration from YAML file."""
        with open(yaml_path) as f:
            data = yaml.safe_load(f)

        allocations = []
        for allocation_data in data.get("allocations", []):
            allocations.append(AllocationConfig(**allocation_data))

        return cls(allocations=allocations)

    def validate(self) -> None:
        """Validate the configuration."""
        if not self.allocations:
            raise ValueError("At least one allocation must be defined")

        for allocation in self.allocations:
            if not allocation.name:
                raise ValueError("Allocation name is required")
            if allocation.variance_factor <= 0:
                raise ValueError(
                    f"Variance factor must be positive for allocation '{allocation.name}'"
                )
            if allocation.frequency not in ["H", "D"]:
                raise ValueError(
                    f"Frequency must be 'H' or 'D' for allocation '{allocation.name}'"
                )
