import threading
import time

import RNS
from RNS.Interfaces.Interface import Interface
from websockets.sync.client import connect
from websockets.sync.connection import Connection


class WebsocketClientInterface(Interface):
    # TODO: required?
    DEFAULT_IFAC_SIZE = 16

    RECONNECT_DELAY_SECONDS = 5

    def __str__(self):
        return f"WebsocketClientInterface[{self.name}/{self.target_url}]"

    def __init__(self, owner, configuration, websocket: Connection = None):
        super().__init__()

        self.owner = owner
        self.parent_interface = None

        self.IN = True
        self.OUT = False
        self.HW_MTU = 262144  # 256KiB
        self.bitrate = 1_000_000_000  # 1Gbps
        self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL

        # parse config
        ifconf = Interface.get_config_obj(configuration)
        self.name = ifconf.get("name")
        self.target_url = ifconf.get("target_url", None)

        # ensure target url is provided
        if self.target_url is None:
            msg = f"target_url is required for interface '{self.name}'"
            raise SystemError(msg)

        # connect to websocket server if an existing connection was not provided
        self.websocket = websocket
        if self.websocket is None:
            thread = threading.Thread(target=self.connect)
            thread.daemon = True
            thread.start()

    # called when a full packet has been received over the websocket
    def process_incoming(self, data):
        # do nothing if offline or detached
        if not self.online or self.detached:
            return

        # update received bytes counter
        self.rxb += len(data)

        # update received bytes counter for parent interface
        if self.parent_interface is not None:
            self.parent_interface.rxb += len(data)

        # send received data to transport instance
        self.owner.inbound(data, self)

    # the running reticulum transport instance will call this method whenever the interface must transmit a packet
    def process_outgoing(self, data):
        # do nothing if offline or detached
        if not self.online or self.detached:
            return

        # send to websocket server
        try:
            self.websocket.send(data)
        except Exception as e:
            RNS.log(
                f"Exception occurred while transmitting via {self!s}",
                RNS.LOG_ERROR,
            )
            RNS.log(f"The contained exception was: {e!s}", RNS.LOG_ERROR)
            return

        # update sent bytes counter
        self.txb += len(data)

        # update received bytes counter for parent interface
        if self.parent_interface is not None:
            self.parent_interface.txb += len(data)

    # connect to the configured websocket server
    def connect(self):
        # do nothing if interface is detached
        if self.detached:
            return

        # connect to websocket server
        try:
            RNS.log(f"Connecting to Websocket for {self!s}...", RNS.LOG_DEBUG)
            self.websocket = connect(
                f"{self.target_url}",
                max_size=None,
                compression=None,
            )
            RNS.log(f"Connected to Websocket for {self!s}", RNS.LOG_DEBUG)
            self.read_loop()
        except Exception as e:
            RNS.log(f"{self} failed with error: {e}", RNS.LOG_ERROR)

        # auto reconnect after delay
        RNS.log(f"Websocket disconnected for {self!s}...", RNS.LOG_DEBUG)
        time.sleep(self.RECONNECT_DELAY_SECONDS)
        self.connect()

    def read_loop(self):
        self.online = True

        try:
            for message in self.websocket:
                self.process_incoming(message)
        except Exception as e:
            RNS.log(f"{self} read loop error: {e}", RNS.LOG_ERROR)

        self.online = False

    def detach(self):
        # mark as offline
        self.online = False

        # close websocket
        if self.websocket is not None:
            self.websocket.close()

        # mark as detached
        self.detached = True


# set interface class RNS should use when importing this external interface
interface_class = WebsocketClientInterface
