import logging
from typing import Any, Dict, List, Optional, Union

from p4p.client.thread import Context, Value

from .utils import unwrap_p4p_structure

logger = logging.getLogger(__name__)


class BridgeIO:
    """
    Handles synchronous Input/Output operations for Process Variables (PVs) via p4p.

    This class serves as the abstraction layer between the logical pv_names used in
    the application (e.g., 'motor_position') and the actual EPICS PV strings
    (e.g., 'IOC:SYS:MOTOR:POS').

    Design Philosophy:
        - **Synchronous Execution:** All methods block until the operation completes
          or times out. This ensures the caller has absolute control over flow.
        - **Fail-Safe:** Errors (disconnects, timeouts) are caught and logged,
          returning safe fallback values (None/False) rather than crashing the thread.

    Attributes:
        cfg (BridgeConfig): Configuration object containing PV definitions.
        ctx (Context): The active p4p client context.
        pv_map (Dict[str, str]): A unified lookup map of {logical_name: full_pv_name}.
    """

    def __init__(self):
        self.ctx = Context("pva")

    def pvget(
        self, pv_names: Union[str, List[str]], timeout: float = 5.0, raw: bool = False
    ) -> Union[Any, Dict[str, Optional[Any]]]:
        """
        Reads PVs synchronously. Supports both single-value and batch reads.

        Args:
            pv_names: A single logical name (str) OR a list of pv_names (List[str]).
            timeout: Maximum seconds to wait for the network response.


        Returns:
            - If 'pv_names' is a str:
            Returns the single value (or None on failure).
            - If 'pv_names' is a list:
            Returns {name: value} dict. None values indicate failure.
        """
        # 1. Normalize Input
        is_single_value = isinstance(pv_names, str)
        if is_single_value:
            pv_names_list = [pv_names]
        else:
            pv_names_list = pv_names

        if not pv_names_list:
            return None if is_single_value else {}

        # 2. Execute Read
        try:
            # throw=False returns Exception objects in the list for specific failures
            values: List[Union[Value, Exception]] = self.ctx.get(
                pv_names_list, throw=False, timeout=timeout
            )
        except Exception as e:
            # Catastrophic failure (Context down, etc.)
            logger.error("Batch read failure: %s", e)
            return None if is_single_value else {n: None for n in pv_names_list}

        if not raw:
            values = self._convert_epics_to_python_types(values)

        # 4. Return (Polymorphic)
        if is_single_value:
            return values[0]

        return dict(zip(pv_names_list, values))

    def pvput(self, data: Dict[str, Any], timeout: float = 5.0) -> bool:
        """
        Writes to a batch of PVs synchronously.

        Blocks until the server confirms the write.
        This guarantees that when this function returns True,
        the value has reached the IOC.

        Args:
            data: A dictionary of {logical_name: value_to_write}.
            timeout: Maximum seconds to wait for write confirmation.

        Returns:
            True: If the write was successful and acknowledged.
            False: If the write timed out or failed (e.g., PV not writable).
        """
        if not data:
            return False

        pvs = []
        vals = []
        for k, v in data.items():
            pvs.append(k)
            vals.append(v)

        try:
            # Block here. wait=True ensures we wait for the server's handshake.
            self.ctx.put(pvs, vals, wait=True, timeout=timeout)
            return True

        except TimeoutError:
            # The server received the request but didn't Ack in time,
            # or the network dropped the packet.
            logger.error("IO Timeout: Write operation stuck for >%.1fs.", timeout)
            return False

        except Exception as e:
            # Catches disconnection errors, permission denied, or type mismatches.
            logger.error("IO Error during write: %s", e)
            return False

    def _convert_epics_to_python_types(self, raw_values):
        processed_values = []
        for raw_val in raw_values:
            if isinstance(raw_val, Exception):
                processed_values.append(None)
            else:
                processed_values.append(unwrap_p4p_structure(raw_val))

        return processed_values
