"""Configuration for multi-pass telemetry generation."""

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, Optional, Union, Any

import yaml


@dataclass
class ElementConfig:
    """Configuration for a single element."""
    anomaly_type: Optional[str] = None
    cost_multiplier: Optional[float] = None
    is_dominant: bool = False


@dataclass
class TelemetryConfig:
    """Multi-pass telemetry generation configuration.

    Supports four-pass architecture:
    - Pass 1: Even distribution at typical_hourly_volume
    - Pass 2: Dominant element redistribution (60% share)
    - Pass 3: Variance application (±variance_pct noise)
    - Pass 4: Cost isolation & anomaly application (spikes only)
    """

    start_date: str  # ISO format: "2025-01-01"
    end_date: str  # ISO format: "2025-03-31"
    typical_hourly_volume: float = 1000.0  # Order of magnitude scale
    variance_pct: float = 50.0  # Noise percentage (±X%)
    elements: Dict[str, Union[str, ElementConfig, Dict[str, Any], None]] = field(default_factory=dict)

    def __post_init__(self):
        """Validate configuration after initialization."""
        self._validate()

    def _validate(self):
        """Validate configuration meets requirements."""
        # Minimum 5 elements required
        if len(self.elements) < 5:
            raise ValueError(
                f"At least 5 elements required, got {len(self.elements)}"
            )

        # Validate date formats
        try:
            datetime.fromisoformat(self.start_date)
            datetime.fromisoformat(self.end_date)
        except ValueError as e:
            raise ValueError(f"Invalid date format (use YYYY-MM-DD): {e}")

        # Validate start < end
        if self.start_date >= self.end_date:
            raise ValueError("start_date must be before end_date")

        # Validate at most one dominant element
        self._validate_single_dominant_element()

        # Validate at most one element with anomaly
        self._validate_single_anomaly_element()

        # Validate anomaly types
        valid_anomalies = {
            "daily_spike",
            "hourly_spike",
            "daily_step_up",
            "daily_step_down",
            "hourly_step_up",
            "hourly_step_down",
            None,
        }
        for element_id, anomaly_config in self.elements.items():
            anomaly_type = self._extract_anomaly_type(anomaly_config)
            if anomaly_type not in valid_anomalies:
                raise ValueError(
                    f"Invalid anomaly type '{anomaly_type}' for element '{element_id}'. "
                    f"Valid types: {', '.join(sorted([t for t in valid_anomalies if t]))}, or null"
                )

    def _validate_single_dominant_element(self):
        """Ensure at most one element is marked dominant."""
        dominant_elements = []

        for element_id, config in self.elements.items():
            element_cfg = self.parse_element_config(config)
            if element_cfg.is_dominant:
                dominant_elements.append(element_id)

        if len(dominant_elements) > 1:
            raise ValueError(
                f"Only one element can be marked as dominant. "
                f"Found {len(dominant_elements)}: {', '.join(dominant_elements)}. "
                f"Please set is_dominant: true for only one element."
            )

    def _validate_single_anomaly_element(self):
        """Ensure at most one element has an anomaly configured.

        Scans all elements upfront and fails fast with descriptive error.
        """
        elements_with_anomalies = []

        for element_id, config in self.elements.items():
            element_cfg = self.parse_element_config(config)
            if element_cfg.anomaly_type is not None:
                elements_with_anomalies.append(element_id)

        if len(elements_with_anomalies) > 1:
            raise ValueError(
                f"Only one element can have an anomaly for clear causality. "
                f"Found {len(elements_with_anomalies)}: {', '.join(elements_with_anomalies)}. "
                f"Please configure anomaly_type for only one element."
            )

    def _extract_anomaly_type(
        self, anomaly_config: Union[str, ElementConfig, Dict[str, Any], None]
    ) -> Optional[str]:
        """Extract anomaly type from config (string, ElementConfig, dict, or None)."""
        if anomaly_config is None:
            return None
        if isinstance(anomaly_config, str):
            return anomaly_config
        if isinstance(anomaly_config, ElementConfig):
            return anomaly_config.anomaly_type
        if isinstance(anomaly_config, dict):
            return anomaly_config.get("type") or anomaly_config.get("anomaly_type")
        return None

    def parse_element_config(
        self, value: Union[str, ElementConfig, Dict[str, Any], None]
    ) -> ElementConfig:
        """Normalize element config to ElementConfig dataclass."""
        if value is None:
            return ElementConfig()
        if isinstance(value, str):
            return ElementConfig(anomaly_type=value)
        if isinstance(value, ElementConfig):
            return value
        if isinstance(value, dict):
            # Handle both 'type' and 'anomaly_type' keys
            anomaly_type = value.get("type") or value.get("anomaly_type")
            return ElementConfig(
                anomaly_type=anomaly_type,
                cost_multiplier=value.get("cost_multiplier"),
                is_dominant=value.get("is_dominant", False),
            )
        return ElementConfig()

    def get_dominant_element(self) -> Optional[str]:
        """Get the element marked as dominant, if any."""
        for element_id, config in self.elements.items():
            element_cfg = self.parse_element_config(config)
            if element_cfg.is_dominant:
                return element_id
        return None

    def get_anomaly_element(self) -> Optional[str]:
        """Get the element with an anomaly configured, if any."""
        for element_id, config in self.elements.items():
            element_cfg = self.parse_element_config(config)
            if element_cfg.anomaly_type is not None:
                return element_id
        return None

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

        # Extract telemetry_generation section
        telemetry_data = data.get("telemetry_generation")
        if not telemetry_data:
            raise ValueError("Missing 'telemetry_generation' section in config")

        return cls(**telemetry_data)

    def to_yaml_dict(self) -> dict:
        """Convert config to YAML-serializable dict."""
        return {
            "telemetry_generation": {
                "start_date": self.start_date,
                "end_date": self.end_date,
                "typical_hourly_volume": self.typical_hourly_volume,
                "variance_pct": self.variance_pct,
                "elements": self.elements,
            }
        }
