"""Configuration API for MultiLat Localizer.

Provides programmatic configuration instead of config.toml files.
"""

import logging
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple, Union

from multilat_solver.common_types import CalibrationType, MessageType
from multilat_solver.input_adapters import (
    INPUT_ADAPTERS,
    FileInputAdapter,
    InputAdapter,
    SerialInputAdapter,
    UDPInputAdapter,
    register_input_adapter,
)
from multilat_solver.output_adapters import OutputAdapter, register_output_adapter

logger = logging.getLogger(__name__)


@dataclass
class LocalizationConfig:
    """Configuration for localization engine.

    @attributes:
        anchor_positions:
            Dictionary of anchor positions.
        origin_coordinates:
            Origin coordinates of the localizer in the format (latitude, longitude, altitude).
        ellipsoid:
            Ellipsoid of the localizer. Default is "wgs84".
        calibration_type:
            Calibration type. Default is "none". "none" for no calibration,\
                "linear" for linear calibration,\
                "quadratic" for quadratic calibration,\
                "cubic" for cubic calibration.
        calibration_params:
            Calibration parameters in the format [k, b] for linear calibration,\
                [a, b, c] for quadratic calibration,\
                [a, b, c, d] for cubic calibration.
        z_sign:
            Z sign. Default is 0.
        min_range:
            Minimum range in meters. Default is 0.0.
        max_range:
            Maximum range in meters. Default is 100.0.
    """

    anchor_positions: Dict[int, Tuple[float, float, float]]
    origin_coordinates: Optional[Tuple[float, float, float]] = (0.0, 0.0, 0.0)
    ellipsoid: str = "wgs84"  # "wgs84" or "ecef"
    calibration_type: CalibrationType = CalibrationType.NONE
    calibration_params: List[float] = field(default_factory=lambda: [1.0, 0.0])
    z_sign: int = 0
    min_range: float = 0.0
    max_range: float = 100.0
    data_expiration_timeout_ms: float = 1000.0

    def __post_init__(self):
        """Validate configuration after initialization."""
        if len(self.anchor_positions) < 3:
            raise ValueError("At least 3 anchor positions are required")

        # Validate calibration params
        if self.calibration_type == CalibrationType.LINEAR and len(self.calibration_params) != 2:
            raise ValueError("Linear calibration requires 2 parameters [k, b]")

        elif self.calibration_type == CalibrationType.QUADRATIC and len(self.calibration_params) != 3:
            raise ValueError("Quadratic calibration requires 3 parameters [a, b, c]")

        elif self.calibration_type == CalibrationType.CUBIC and len(self.calibration_params) != 4:
            raise ValueError("Cubic calibration requires 4 parameters [a, b, c, d]")


@dataclass
class InputConfig:
    """Input adapter configuration wrapper."""

    type_name: str  # Adapter type name (e.g., "serial", "mqtt", "udp", "file", or custom)
    adapter: Any = None


@dataclass
class SumOutputConfig:
    """Summary output adapter configuration wrapper."""

    types: List[str] = field(default_factory=list)  # Single type or list for multiple adapters
    adapters: Dict[str, Any] = field(default_factory=dict)
    # Adapter-specific configs (only one will be used based on type)
    configs: Dict[str, Any] = field(default_factory=dict)


@dataclass
class MultiLatLocalizerConfig:
    """Complete configuration for MultiLat Localizer."""

    localization: LocalizationConfig
    input: InputConfig
    output: Any

    @classmethod
    def from_dict(cls, config_dict: dict) -> "MultiLatLocalizerConfig":
        """Create configuration from dictionary (for backward compatibility with TOML)."""
        # Parse localization config
        loc_dict = config_dict.get("localization", {})
        anchor_positions = {
            int(k): tuple(v) if isinstance(v, list) else v
            for k, v in loc_dict.get("anchor_positions", {}).items()
        }

        origin_coords = loc_dict.get("origin_coordinates")
        if origin_coords and isinstance(origin_coords, dict):
            origin_coords = (
                origin_coords.get("lat", 0.0),
                origin_coords.get("lon", 0.0),
                origin_coords.get("alt", 0.0),
            )
        else:
            origin_coords = (0.0, 0.0, 0.0)

        localization = LocalizationConfig(
            anchor_positions=anchor_positions,
            origin_coordinates=origin_coords,
            ellipsoid=loc_dict.get("ellipsoid", "wgs84"),
            calibration_type=CalibrationType(loc_dict.get("calibration_type", "none")),
            calibration_params=loc_dict.get("calibration_params", [1.0, 0.0]),
            z_sign=loc_dict.get("z_sign", 0),
            min_range=loc_dict.get("min_range", 0.0),
            max_range=loc_dict.get("max_range", 100.0),
        )

        # Parse input config
        input_dict = config_dict.get("input", {})
        input_type = input_dict.get("str", "serial").lower()

        # Check if adapter type is registered
        if input_type not in INPUT_ADAPTERS:
            raise ValueError(
                f"Unknown input adapter type: {input_type}. "
                f"Available types: {list(INPUT_ADAPTERS.keys())}"
                f"Register adapter using register_input_adapter"
                f"Example: register_input_adapter('my', MyInputAdapter, MyInputConfig(my_param=1))"
            )

        # Map known adapter types to their config classes for backward compatibility
        config_class_map = {
            "serial": SerialInputAdapter,
            "udp": UDPInputAdapter,
            "file": FileInputAdapter,
        }

        # Use predefined config class if available, otherwise use dict directly
        if input_type in config_class_map:
            config_class = config_class_map[input_type]
            if input_type == "serial":
                adapter_obj = config_class(
                    port=input_dict.get("port", "/dev/ttyUSB0"),
                    baud=input_dict.get("baud", 460800),
                    timeout=input_dict.get("timeout", 1.0),
                )
            elif input_type == "udp":
                adapter_obj = config_class(
                    host=input_dict.get("host", "0.0.0.0"),
                    port=input_dict.get("port", 5005),
                )
            elif input_type == "file":
                adapter_obj = config_class(
                    filepath=input_dict.get("filepath", "distances.json"),
                    poll_interval=input_dict.get("poll_interval", 1.0),
                )
        else:
            # For custom adapters, use dict directly (will be converted in app.py)
            adapter_obj = {k: v for k, v in input_dict.items() if k != "str"}

        input_config = InputConfig(type_name=input_type, adapter=adapter_obj)

        # Parse output config
        output_dict = config_dict.get("output", {})
        output_str = output_dict.get("str", "console").lower()

        # Handle multiple output types
        if isinstance(output_str, str) and "," in output_str:
            output_strs = [t.strip() for t in output_str.split(",")]
        elif isinstance(output_str, list):
            output_strs = output_str
        else:
            output_strs = [output_str]

        output_config = SumOutputConfig(
            types=output_strs,
            adapters=output_dict.get("adapters", {}),
            configs=output_dict.get("configs", {}),
        )

        return cls(
            localization=localization,
            input=input_config,
            output=output_config,
        )


class ConfigBuilder:
    """Builder class for creating MultiLat Localizer configurations programmatically."""

    def __init__(self):
        """Initialize ConfigBuilder."""
        self.__localization: LocalizationConfig | None = None
        self.__input: InputConfig | None = None
        self.__output: SumOutputConfig | None = None
        self._message_type: MessageType = MessageType.BOTH

    def with_localization(
        self,
        localization: LocalizationConfig | None = None,
        anchor_positions: Dict[int, Tuple[float, float, float]] = {},
        origin_coordinates: Optional[Tuple[float, float, float]] = (0.0, 0.0, 0.0),
        ellipsoid: str = "wgs84",
        calibration_type: Union[CalibrationType, str] = CalibrationType.NONE,
        calibration_params: Optional[List[float]] = [],
        z_sign: int = 0,
        min_range: float = 0.0,
        max_range: float = 100.0,
    ) -> "ConfigBuilder":
        """Configure localization engine."""
        if localization is not None:
            self.__localization = localization
            return self

        if isinstance(calibration_type, str):
            calibration_type = CalibrationType(calibration_type)
        logger.debug(f"anchor_positions {anchor_positions}")
        self.__localization = LocalizationConfig(
            anchor_positions=anchor_positions,
            origin_coordinates=origin_coordinates,
            ellipsoid=ellipsoid,
            calibration_type=calibration_type,
            calibration_params=calibration_params or [1.0, 0.0],
            z_sign=z_sign,
            min_range=min_range,
            max_range=max_range,
        )
        return self

    def with_serial_input(
        self,
        port: str = "/dev/ttyUSB0",
        baud: int = 460800,
        timeout: float = 1.0,
    ) -> "ConfigBuilder":
        """Configure serial input adapter."""
        self.__input = InputConfig(
            type_name="serial",
            adapter=SerialInputAdapter(port=port, baud=baud, timeout=timeout),
        )
        return self

    def with_udp_input(
        self,
        host: str = "0.0.0.0",
        port: int = 5005,
    ) -> "ConfigBuilder":
        """Configure UDP input adapter."""
        self.__input = InputConfig(type_name="udp", adapter=UDPInputAdapter(host=host, port=port))
        return self

    def with_file_input(
        self,
        filepath: str = "distances.json",
        poll_interval: float = 1.0,
    ) -> "ConfigBuilder":
        """Configure file input adapter."""
        self.__input = InputConfig(
            type_name="file",
            adapter=FileInputAdapter(filepath=filepath, poll_interval=poll_interval),
        )
        return self

    def with_input(self, input_str: str, adapter: Any) -> "ConfigBuilder":
        """Configure input adapter."""
        if self.__input is None:
            self.__input = InputConfig(type_name=input_str)
        if not issubclass(type(adapter), InputAdapter):
            raise ValueError("Adapter must be a subclass of InputAdapter")
        self.__input.adapter = adapter
        return self

    def with_output(self, output_str: str, adapter: Any) -> "ConfigBuilder":
        """Configure output adapter."""
        if self.__output is None:
            self.__output = SumOutputConfig()
        if not issubclass(type(adapter), OutputAdapter):
            raise ValueError("Adapter must be a subclass of BaseOutputConfig")
        self.__output.types.append(output_str)
        self.__output.adapters[output_str] = adapter
        return self

    def register_output(self, output_str: str, adapter: Any) -> "ConfigBuilder":
        """Register output adapter."""
        if adapter is None:
            raise ValueError("Adapter cannot be None")
        if not issubclass(type(adapter), OutputAdapter):
            raise ValueError("Adapter must be a subclass of OutputAdapter")
        register_output_adapter(output_str, adapter)
        if self.__output is None:
            self.__output = SumOutputConfig()
        self.__output.adapters[output_str] = adapter
        return self

    def register_input(self, adapter_type: str, adapter: Any, config: Any = None) -> "ConfigBuilder":
        """Register input adapter."""
        register_input_adapter(adapter_type, adapter)
        if self.__input is None:
            self.__input = InputConfig(type_name=adapter_type, config=config.__dict__)
        return self

    def build(self) -> MultiLatLocalizerConfig:
        """Build the configuration."""
        if self.__localization is None:
            raise ValueError("Localization configuration is required")
        if self.__input is None:
            raise ValueError("Input configuration is required")
        if self.__output is None:
            raise ValueError("Output configuration is required")

        assert issubclass(type(self.__input.adapter), InputAdapter)

        return MultiLatLocalizerConfig(
            localization=self.__localization,
            input=self.__input,
            output=self.__output,
        )
