# Copyright 2026 Acute Technology Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""Data Generator Bus Class."""

from typing import TYPE_CHECKING, ClassVar

import betterproto

import aqpxlib.message as pxmsg
from aqpxlib.abstract import PxAbstractBus, PxAbstractDevice
from aqpxlib.data_generator.data_generator_counter import PxDataGeneratorCounter

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxDataGeneratorBus(PxAbstractBus):
    """Data Generator Bus Class."""

    # Used to identify the class
    identifier: str = "data_generator"

    controller_class: type[PxAbstractDevice] = PxDataGeneratorCounter

    # Data Generator supports only a single counter controller, no targets
    _available_devices: ClassVar[list[type[PxAbstractDevice]]] = [
        PxDataGeneratorCounter,
    ]

    # Used to query the bus type from the protobuf messages
    protobuf_key: str = "data_generator"

    def __init__(self, exerciser: "AqProtocolExerciser", *args, **kwargs) -> None:
        """Initialize a Data Generator bus.

        Args:
            exerciser: An exerciser instance used to send requests to the server.
            *args: Additional arguments.
            **kwargs: Keyword arguments for channel configuration.
                Accepts ch0-ch7 (all optional int) for channel pin assignments,
                or config (PxHandle) + handle_index for reconstruction from
                existing configuration.

        Raises:
            ValueError: If the bus cannot be created.

        """
        super().__init__(exerciser)

        if "config" in kwargs:
            assert isinstance(kwargs["config"], pxmsg.PxHandle)
            dg_handle_config: pxmsg.PxHandle = kwargs["config"]
            dg_channel = dg_handle_config.data_generator
            self.ch0 = dg_channel.ch0
            self.ch1 = dg_channel.ch1
            self.ch2 = dg_channel.ch2
            self.ch3 = dg_channel.ch3
            self.ch4 = dg_channel.ch4
            self.ch5 = dg_channel.ch5
            self.ch6 = dg_channel.ch6
            self.ch7 = dg_channel.ch7
            if "handle_index" in kwargs:
                self._handle_index = kwargs["handle_index"]
                return
        else:
            self.ch0 = kwargs.get("ch0")
            self.ch1 = kwargs.get("ch1")
            self.ch2 = kwargs.get("ch2")
            self.ch3 = kwargs.get("ch3")
            self.ch4 = kwargs.get("ch4")
            self.ch5 = kwargs.get("ch5")
            self.ch6 = kwargs.get("ch6")
            self.ch7 = kwargs.get("ch7")

        handle_index = self._find_or_create_bus(**self._channel_kwargs())
        if handle_index == -1:
            err_msg = "Failed to create Data Generator bus"
            raise ValueError(err_msg)

        self.handle_index = handle_index

    def _channel_kwargs(self) -> dict:
        """Build kwargs dict with only the channels that are set."""
        kw = {}
        for i in range(8):
            val = getattr(self, f"ch{i}")
            if val is not None:
                kw[f"ch{i}"] = val
        return kw

    def attach_controller(self, controller: PxAbstractDevice):
        """Attach a controller to the Data Generator bus.

        Only a single controller is allowed.

        Args:
            controller: The controller device to attach.

        Raises:
            ValueError: If a controller is already attached or if the device
                is not a valid Data Generator controller.

        """
        existing = self.get_controllers()
        if len(existing) > 0:
            err_msg = "Data Generator bus only supports a single controller"
            raise ValueError(err_msg)

        super().attach_controller(controller)

    def attach_target(self, target: PxAbstractDevice):
        """Reject target attachment — Data Generator does not support targets.

        Args:
            target: The target device (will always be rejected).

        Raises:
            ValueError: Always raised — targets are not supported.

        """
        err_msg = "Data Generator bus does not support target devices"
        raise ValueError(err_msg)

    def _to_protobuf_handle_config(self, *args, **kwargs) -> pxmsg.PxHandle:
        """Convert the bus to a protobuf structure.

        Args:
            *args: Additional arguments.
            **kwargs: Channel keyword arguments (ch0-ch7).

        Returns:
            pxmsg.PxHandle: The protobuf structure of the bus.

        """
        channel_kwargs = {k: v for k, v in kwargs.items() if k.startswith("ch") and v is not None}

        return pxmsg.PxHandle(
            data_generator=pxmsg.DataGeneratorChannel(**channel_kwargs),
        )

    def __eq__(self, other: object) -> bool:
        """Check if the bus is equal to another bus.

        Args:
            other: The other bus to compare with or a protobuf structure of the bus.

        Returns:
            bool: True if the bus is equal to the other bus, False otherwise.

        """
        if isinstance(other, PxDataGeneratorBus):
            return all(getattr(self, f"ch{i}") == getattr(other, f"ch{i}") for i in range(8))

        if isinstance(other, pxmsg.DataGeneratorChannel):
            return all(getattr(self, f"ch{i}") == getattr(other, f"ch{i}") for i in range(8))

        if isinstance(other, pxmsg.PxHandle):
            _, channel = betterproto.which_one_of(other, "protocol")
            return self == channel

        return False

    def __str__(self) -> str:
        """Return string representation of the bus."""
        channels = ", ".join(
            f"CH{i}: {getattr(self, f'ch{i}')}" for i in range(8) if getattr(self, f"ch{i}") is not None
        )
        return f"Data Generator Bus ({channels})"

    def __repr__(self) -> str:
        """Representation of the bus."""
        return self.__str__()


__all__ = [
    "PxDataGeneratorBus",
]
