from __future__ import annotations

import threading
import time
from typing import Optional

from .bus import CANBusEmulation, CANBusParticipant
from .can import CANError, CANFrame, ReadError, SocketCANInterface


class ExternalCANBridgeParticipant(CANBusParticipant):
    def __init__(self, bridge: "ExternalCANBridge") -> None:
        super().__init__()
        self._bridge = bridge

    @property
    def arbitration_ids(self):
        return set()

    def feed(self, frame: CANFrame, bus: CANBusEmulation) -> None:
        self._bridge._handle_bus_frame(frame)


class ExternalCANBridge:
    _ECHO_SUPPRESS_WINDOW_S = 0.075
    _MAX_TRACKED_TX_FRAMES = 256

    def __init__(
        self,
        bus: CANBusEmulation,
        interface: str,
        bitrate: int,
        fd_mode: bool = False,
        data_bitrate: Optional[int] = None,
        listen_only: bool = False,
        termination: Optional[bool] = None,
        log: bool = False,
    ) -> None:
        self.bus = bus
        self.interface = interface
        self.bitrate = bitrate
        self.fd_mode = fd_mode
        self.data_bitrate = data_bitrate
        self.listen_only = listen_only
        self.termination = bool(termination) if termination is not None else None
        self.log = log
        self._can_interface = SocketCANInterface(interface)
        self._stop = threading.Event()
        self._thread: Optional[threading.Thread] = None
        self._participant = ExternalCANBridgeParticipant(self)
        self._write_lock = threading.Lock()
        self._last_rx: Optional[float] = None
        self._last_tx: Optional[float] = None
        self._rx_bytes = 0
        self._tx_bytes = 0
        self._recent_tx: list[tuple[float, int, int, bool, bool, bool, bytes]] = []
        self._recent_tx_lock = threading.Lock()
        self._suppressed_echo_frames = 0

    def _log(self, message: str) -> None:
        if self.log:
            print(message)

    @staticmethod
    def _frame_signature(frame: CANFrame) -> tuple[int, int, bool, bool, bool, bytes]:
        return (
            frame.arbitration_id,
            frame.dlc,
            bool(getattr(frame, "is_fd", False)),
            bool(getattr(frame, "bitrate_switch", False)),
            bool(getattr(frame, "error_state_indicator", False)),
            bytes(frame.data[: frame.dlc]),
        )

    def _record_transmitted_frame(self, frame: CANFrame, now: float) -> None:
        signature = self._frame_signature(frame)
        cutoff = now - self._ECHO_SUPPRESS_WINDOW_S
        with self._recent_tx_lock:
            self._recent_tx = [entry for entry in self._recent_tx if entry[0] >= cutoff]
            self._recent_tx.append((now, *signature))
            if len(self._recent_tx) > self._MAX_TRACKED_TX_FRAMES:
                del self._recent_tx[: len(self._recent_tx) - self._MAX_TRACKED_TX_FRAMES]

    def _is_recent_self_echo(self, frame: CANFrame, now: float) -> bool:
        signature = self._frame_signature(frame)
        cutoff = now - self._ECHO_SUPPRESS_WINDOW_S
        with self._recent_tx_lock:
            self._recent_tx = [entry for entry in self._recent_tx if entry[0] >= cutoff]
            for index, (_timestamp, arbitration_id, dlc, is_fd, brs, esi, payload) in enumerate(self._recent_tx):
                if (arbitration_id, dlc, is_fd, brs, esi, payload) == signature:
                    self._recent_tx.pop(index)
                    return True
        return False

    def start(self) -> bool:
        if self._thread and self._thread.is_alive():
            return True
        self._stop.clear()
        self._can_interface.open(
            self.bitrate,
            fd=self.fd_mode,
            data_bitrate=self.data_bitrate if self.fd_mode else None,
            listen_only=self.listen_only,
            termination=self.termination,
        )
        self.bus.attach(self._participant)
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()
        print(f"DEBUG: External CAN bridge started on {self.interface}", flush=True)
        return True

    def stop(self) -> None:
        self._stop.set()
        try:
            self._can_interface.close()
        except Exception:
            pass
        self.bus.detach(self._participant)
        if self._thread is not None:
            self._thread.join(timeout=1.0)
            self._thread = None
        print("DEBUG: External CAN bridge stopped", flush=True)

    def _handle_bus_frame(self, frame: CANFrame) -> None:
        if self._stop.is_set():
            return
        if self.listen_only:
            return
        with self._write_lock:
            try:
                self._can_interface.write(frame)
            except CANError as exc:
                self._log(f"External CAN write failed: {exc}")
                return
        now = time.time()
        self._last_tx = now
        self._tx_bytes += frame.dlc
        self._record_transmitted_frame(frame, now)

    def _run(self) -> None:
        while not self._stop.is_set():
            try:
                frame = self._can_interface.read(timeout_ms=200)
            except ReadError:
                continue
            except CANError as exc:
                self._log(f"External CAN read failed: {exc}")
                time.sleep(0.1)
                continue
            now = time.time()
            if self._is_recent_self_echo(frame, now):
                self._suppressed_echo_frames += 1
                continue
            self._last_rx = now
            self._rx_bytes += frame.dlc
            self.bus.write(frame, originator=self._participant)

    def ui_snapshot(self) -> dict:
        return {
            "connected": self._thread is not None and self._thread.is_alive(),
            "clients": 0,
            "last_rx": self._last_rx,
            "last_tx": self._last_tx,
            "rx_bytes": self._rx_bytes,
            "tx_bytes": self._tx_bytes,
            "suppressed_echo_frames": self._suppressed_echo_frames,
        }
