from __future__ import annotations

import queue
import socket
import threading
import time
from dataclasses import dataclass
from typing import Any, Callable, Optional

from ..core.bus import CANBusEmulation, CANBusParticipant
from ..core.can import CANFrame
from ..core.isotp import ISOTPAction, ISOTPTransceiver


HSFZ_PORT_TCP = 6801
HSFZ_PORT_UDP = 6811
HSFZ_TYPE_DIAGNOSTIC_REQ_RES = 0x0001
HSFZ_TYPE_ACKNOWLEDGE_TRANSFER = 0x0002
HSFZ_BROADCAST_ARBITRATION = 0xDF
HSFZ_MAX_ECHO_PAYLOAD = 5
HSFZ_REQUEST_VEHICLE_ANNOUNCEMENT = bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x11])
HSFZ_BROADCAST_UDP = "255.255.255.255"
HSFZ_BROADCAST_LINK_LOCAL_UDP = "169.254.255.255"


@dataclass
class HSFZMessage:
    msg_type: int
    source: int
    destination: int
    payload: list[int]

    @property
    def length(self) -> int:
        return 2 + len(self.payload)

    @property
    def bytes(self) -> bytes:
        length_bytes = self.length.to_bytes(4, "big")
        type_bytes = self.msg_type.to_bytes(2, "big")
        return length_bytes + type_bytes + bytes([self.source, self.destination]) + bytes(self.payload)

    @property
    def echo(self) -> "HSFZMessage":
        payload = self.payload[:HSFZ_MAX_ECHO_PAYLOAD]
        return HSFZMessage(
            msg_type=HSFZ_TYPE_ACKNOWLEDGE_TRANSFER,
            source=self.source,
            destination=self.destination,
            payload=payload,
        )

    @property
    def is_broadcast(self) -> bool:
        return self.destination == HSFZ_BROADCAST_ARBITRATION

    @staticmethod
    def from_socket(client: socket.socket) -> Optional["HSFZMessage"]:
        header = _read_exact(client, 6)
        if not header:
            return None
        length = int.from_bytes(header[:4], "big")
        msg_type = int.from_bytes(header[4:6], "big")
        if length == 0:
            return None
        address_bytes = _read_exact(client, 2)
        if not address_bytes:
            return None
        source, destination = address_bytes[0], address_bytes[1]
        payload_length = max(length - 2, 0)
        payload = list(_read_exact(client, payload_length)) if payload_length else []
        return HSFZMessage(msg_type=msg_type, source=source, destination=destination, payload=payload)


def _read_exact(client: socket.socket, length: int) -> bytes:
    if length <= 0:
        return b""
    buffer = bytearray()
    while len(buffer) < length:
        try:
            chunk = client.recv(length - len(buffer))
        except (ConnectionResetError, OSError):
            return b""
        if not chunk:
            return b""
        buffer.extend(chunk)
    return bytes(buffer)


def _sanitize_hex_string(value: str, length: int, fallback: str) -> str:
    hex_chars = "".join(ch for ch in value if ch.isalnum())
    if len(hex_chars) < length:
        return fallback
    return hex_chars[:length].upper()


def _sanitize_vin(vin: str) -> str:
    vin = "".join(ch for ch in vin if ch.isalnum()).upper()
    if len(vin) >= 17:
        return vin[:17]
    return (vin + ("0" * 17))[:17]


def _build_vehicle_announcement(vin: str, diag_addr: str, bmw_mac: str) -> bytes:
    info = f"DIAGADR{diag_addr}BMWMAC{bmw_mac}BMWVIN{vin}"
    payload = info.encode("utf-8")
    length = 0x32
    if len(payload) != length:
        payload = payload[:length].ljust(length, b"0")
    return bytes([0x00, 0x00, 0x00, length, 0x00, 0x11]) + payload


class HSFZParticipant(CANBusParticipant):
    def __init__(self, server: "HSFZServer") -> None:
        super().__init__()
        self.server = server

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

    def feed(self, frame: CANFrame, bus: CANBusEmulation) -> None:
        self.server.handle_incoming_frame(frame)


class HSFZRequest:
    def __init__(self, source: int, destination: int, payload: list[int]) -> None:
        self.source = source
        self.destination = destination
        self.payload = payload
        self.is_broadcast = destination == HSFZ_BROADCAST_ARBITRATION
        self.tx_isotp = ISOTPTransceiver(block_size=0, separation_time_ms=1)
        self.rx_isotp: dict[int, ISOTPTransceiver] = {}
        self.responses: "queue.Queue[tuple[int, list[int]]]" = queue.Queue()
        self.response_pending = False
        self.responses_received = 0

    def handle_tx_flow_control(self, frame: CANFrame) -> Optional[list[list[int]]]:
        if self.tx_isotp.state != "sending":
            return None
        try:
            action = self.tx_isotp.did_read(list(frame.data[:frame.dlc]))
        except Exception:
            return None
        if action.kind != "write_frames" or action.frames is None:
            return None
        return action.frames

    def handle_rx_frame(self, frame: CANFrame) -> Optional[list[list[int]]]:
        transceiver = self.rx_isotp.get(frame.arbitration_id)
        payload = list(frame.data[:frame.dlc])
        if transceiver is None or transceiver.frame_length != len(payload):
            transceiver = ISOTPTransceiver(block_size=0, separation_time_ms=1, frame_length=len(payload))
            self.rx_isotp[frame.arbitration_id] = transceiver
        try:
            action = transceiver.did_read(payload)
        except Exception:
            return None
        if action.kind == "process" and action.bytes is not None:
            self.responses.put((frame.arbitration_id, action.bytes))
            return None
        if action.kind == "write_frames" and action.frames is not None:
            return action.frames
        return None


class HSFZServer:
    def __init__(
        self,
        bus: CANBusEmulation,
        host: str,
        port: int,
        log: bool = False,
        vin: str = "WBA45678901234567",
        diag_addr: str = "10",
        bmw_mac: str = "112233445566",
        event_sink: Optional[Callable[[dict[str, Any]], None]] = None,
    ) -> None:
        self.bus = bus
        self.host = host
        self.port = port
        self.log = log
        self._event_sink = event_sink
        self.vin = _sanitize_vin(vin)
        self.diag_addr = _sanitize_hex_string(diag_addr, length=2, fallback="10")
        self.bmw_mac = _sanitize_hex_string(bmw_mac, length=12, fallback="112233445566")
        self.vehicle_announcement = _build_vehicle_announcement(self.vin, self.diag_addr, self.bmw_mac)
        self._socket: Optional[socket.socket] = None
        self._stop = threading.Event()
        self._thread: Optional[threading.Thread] = None
        self._client: Optional[socket.socket] = None
        self._client_peer: Optional[str] = None
        self._client_count = 0
        self._last_rx: Optional[float] = None
        self._last_tx: Optional[float] = None
        self._rx_bytes = 0
        self._tx_bytes = 0
        self._write_lock = threading.Lock()
        self._participant = HSFZParticipant(self)
        self._request_lock = threading.Lock()
        self._active_request: Optional[HSFZRequest] = None
        self._udp_thread: Optional[threading.Thread] = None
        self._udp_socket: Optional[socket.socket] = None

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

    def _emit_event(self, kind: str, **payload: Any) -> None:
        if self._event_sink is None:
            return
        event_payload: dict[str, Any] = {"kind": kind, "timestamp": time.time()}
        event_payload.update(payload)
        try:
            self._event_sink(event_payload)
        except Exception:
            pass

    def start(self) -> bool:
        if self._thread and self._thread.is_alive():
            return True
        self._stop.clear()
        # Bind TCP socket synchronously before starting thread
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        for attempt in range(10):
            try:
                self._socket.bind((self.host, self.port))
                break
            except OSError as e:
                self._log(f"HSFZ: bind attempt {attempt + 1} failed: {e}")
                if attempt == 9:
                    return False
                time.sleep(0.1)
        self._socket.listen(1)
        self._socket.settimeout(0.5)
        self._log(f"HSFZ: listening on {self.host}:{self.port}")
        print(f"DEBUG: HSFZ TCP server started on {self.host}:{self.port}", flush=True)
        self.bus.attach(self._participant)
        self._start_udp_service()
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()
        return True

    def stop(self) -> None:
        self._stop.set()
        if self._client:
            try:
                self._client.shutdown(socket.SHUT_RDWR)
            except OSError:
                pass
            try:
                self._client.close()
            except OSError:
                pass
            self._client = None
            self._client_peer = None
        if self._socket:
            try:
                self._socket.shutdown(socket.SHUT_RDWR)
            except OSError:
                pass
            try:
                self._socket.close()
            except OSError:
                pass
            self._socket = None
        if self._udp_socket:
            try:
                self._udp_socket.close()
            except OSError:
                pass
            self._udp_socket = None
        if self._thread is not None:
            self._thread.join(timeout=1.0)
            self._thread = None
        if self._udp_thread is not None:
            self._udp_thread.join(timeout=1.0)
            self._udp_thread = None
        print("DEBUG: HSFZ server stopped", flush=True)

    def handle_incoming_frame(self, frame: CANFrame) -> None:
        with self._request_lock:
            request = self._active_request
        if request is None:
            return
        tx_frames = request.handle_tx_flow_control(frame)
        if tx_frames:
            self._send_can_frames(request.destination, tx_frames)
            return
        rx_frames = request.handle_rx_frame(frame)
        if rx_frames:
            self._send_can_frames(request.destination, rx_frames)

    def _run(self) -> None:
        if self._socket is None:
            return
        while not self._stop.is_set():
            try:
                client, addr = self._socket.accept()
            except OSError:
                if self._stop.is_set():
                    break
                continue
            self._client = client
            self._client_peer = f"{addr[0]}:{addr[1]}"
            self._client_count = 1
            self._log(f"HSFZ: client connected from {addr}")
            self._emit_event("connection_opened", peer=self._client_peer)
            client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
            try:
                self._serve_client(client)
            except (ConnectionResetError, OSError) as exc:
                if not self._stop.is_set():
                    self._log(f"HSFZ: client connection error: {exc}")
                    self._emit_event("connection_error", message=str(exc), peer=self._client_peer or "unknown")
            self._log("HSFZ: client disconnected")
            self._emit_event("connection_closed", peer=self._client_peer or "unknown")
            self._client_peer = None

    def _serve_client(self, client: socket.socket) -> None:
        try:
            while True:
                message = HSFZMessage.from_socket(client)
                if message is None:
                    return
                self._last_rx = time.time()
                self._rx_bytes += len(message.bytes)
                self._emit_event("transport_rx", bytes=len(message.bytes), peer=self._client_peer or "unknown")
                if message.msg_type != HSFZ_TYPE_DIAGNOSTIC_REQ_RES:
                    continue
                self._send_hsfz(client, message.echo)
                self._handle_request(client, message)
        finally:
            self._client = None
            self._client_count = 0
            with self._request_lock:
                self._active_request = None

    def _start_udp_service(self) -> None:
        thread = threading.Thread(target=self._udp_loop, daemon=True)
        self._udp_thread = thread
        thread.start()

    def _udp_loop(self) -> None:
        try:
            self._send_initial_announcement()
        except Exception as exc:
            self._log(f"HSFZ: UDP announcement failed: {exc}")
        udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._udp_socket = udp
        udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        udp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        try:
            udp.bind(("0.0.0.0", HSFZ_PORT_UDP))
        except OSError as exc:
            self._log(f"HSFZ: UDP bind failed on 0.0.0.0:{HSFZ_PORT_UDP}: {exc}")
            return
        self._log(f"HSFZ: UDP listening on 0.0.0.0:{HSFZ_PORT_UDP}")
        print(f"DEBUG: HSFZ UDP server started on 0.0.0.0:{HSFZ_PORT_UDP}", flush=True)
        udp.settimeout(0.5)
        while not self._stop.is_set():
            try:
                data, addr = udp.recvfrom(256)
            except OSError:
                if self._stop.is_set():
                    break
                continue
            if data != HSFZ_REQUEST_VEHICLE_ANNOUNCEMENT:
                continue
            try:
                udp.sendto(self.vehicle_announcement, addr)
            except OSError:
                continue

    def _send_initial_announcement(self) -> None:
        udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        try:
            udp.bind((self.host if self.host != "0.0.0.0" else "", 0))
        except OSError:
            pass
        broadcast = HSFZ_BROADCAST_LINK_LOCAL_UDP if self.host.startswith("169.254.") else HSFZ_BROADCAST_UDP
        for _ in range(3):
            try:
                udp.sendto(self.vehicle_announcement, (broadcast, HSFZ_PORT_UDP))
            except OSError:
                pass
            time.sleep(0.333)
        udp.close()

    def _handle_request(self, client: socket.socket, message: HSFZMessage) -> None:
        request = HSFZRequest(message.source, message.destination, message.payload)
        with self._request_lock:
            self._active_request = request
        action = request.tx_isotp.write(message.payload)
        self._emit_event(
            "diagnostic_request",
            peer=self._client_peer or "unknown",
            source=message.source,
            destination=message.destination,
            bytes=list(message.payload),
        )
        if action.kind == "write_frames" and action.frames is not None:
            self._send_can_frames(request.destination, action.frames)
        timeout = 1.0
        end_time = time.time() + timeout
        while True:
            remaining = end_time - time.time()
            if remaining <= 0:
                break
            try:
                source_id, uds_bytes = request.responses.get(timeout=remaining)
            except queue.Empty:
                break
            if not uds_bytes:
                continue
            is_negative = len(uds_bytes) >= 3 and uds_bytes[0] == 0x7F
            is_response_pending = is_negative and uds_bytes[2] == 0x78
            if is_response_pending and not request.is_broadcast:
                end_time = time.time() + 5.0
            if request.is_broadcast and is_negative:
                continue
            response = HSFZMessage(
                msg_type=HSFZ_TYPE_DIAGNOSTIC_REQ_RES,
                source=source_id & 0xFF,
                destination=request.source & 0xFF,
                payload=uds_bytes,
            )
            self._send_hsfz(client, response)
            self._emit_event(
                "diagnostic_response",
                peer=self._client_peer or "unknown",
                source=source_id,
                destination=request.source,
                bytes=list(uds_bytes),
            )
            if not request.is_broadcast and not is_response_pending:
                break
        with self._request_lock:
            self._active_request = None

    def _send_can_frames(self, arbitration_id: int, frames: list[list[int]]) -> None:
        for frame_bytes in frames:
            payload = list(frame_bytes)
            dlc = len(payload)
            frame = CANFrame(arbitration_id=arbitration_id, data=payload, dlc=dlc)
            self.bus.write(frame, originator=self._participant)

    def _send_hsfz(self, client: socket.socket, message: HSFZMessage) -> None:
        data = message.bytes
        with self._write_lock:
            try:
                client.sendall(data)
                self._last_tx = time.time()
                self._tx_bytes += len(data)
                self._emit_event("transport_tx", bytes=len(data), peer=self._client_peer or "unknown")
            except OSError:
                self._client = None

    def ui_snapshot(self) -> dict:
        return {
            "connected": self._client is not None,
            "clients": self._client_count,
            "last_rx": self._last_rx,
            "last_tx": self._last_tx,
            "rx_bytes": self._rx_bytes,
            "tx_bytes": self._tx_bytes,
        }
