import logging
import re
from datetime import datetime
from threading import Thread
from time import sleep

import pytz
import telnetlib3

from core.config import SERVER_OWNER_CALLSIGN
from data.spot import Spot
from providers.spot.spot_provider import SpotProvider


class DXCluster(SpotProvider):
    """Spot provider for a DX Cluster. Hostname, port, login_prompt, login_callsign and allow_rbn_spots are provided in config.
    See config-example.yml for examples."""

    _LINE_PATTERN_EXCLUDE_RBN = re.compile(
        r"^DX de ([a-z0-9/]+):\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
        re.IGNORECASE)
    _LINE_PATTERN_ALLOW_RBN = re.compile(
        r"^DX de ([a-z0-9/]+)-?#?:\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
        re.IGNORECASE)

    def __init__(self, provider_config):
        """Constructor requires hostname and port"""

        name = provider_config["name"] if "name" in provider_config else "Cluster"
        super().__init__(name, provider_config)
        self._hostname = provider_config["host"]
        self._port = provider_config["port"]
        self._login_prompt = provider_config["login_prompt"] if "login_prompt" in provider_config else "login:"
        self._login_callsign = provider_config[
            "login_callsign"] if "login_callsign" in provider_config else SERVER_OWNER_CALLSIGN
        self._allow_rbn_spots = provider_config["allow_rbn_spots"] if "allow_rbn_spots" in provider_config else False
        self._spot_line_pattern = self._LINE_PATTERN_ALLOW_RBN if self._allow_rbn_spots else self._LINE_PATTERN_EXCLUDE_RBN
        self._telnet = None
        self._thread = Thread(target=self._handle, name=f"DXClusterSpotProvider-{self.name}")
        self._thread.daemon = True
        self._running = True

    def start(self):
        self._thread.start()

    def stop(self):
        self._running = False
        self._telnet.close()
        self._thread.join()

    def _handle(self):
        while self._running:
            connected = False
            while not connected and self._running:
                try:
                    self.status = "Connecting"
                    logging.info("DX Cluster " + self._hostname + " connecting...")
                    self._telnet = telnetlib3.Telnet(self._hostname, self._port)
                    self._telnet.read_until(self._login_prompt.encode("latin-1"))
                    self._telnet.write((self._login_callsign + "\n").encode("latin-1"))
                    connected = True
                    logging.info("DX Cluster " + self._hostname + " connected.")
                except ConnectionRefusedError:
                    self.status = "Error"
                    logging.warning("Connection refused to DX cluster " + self._hostname)
                    sleep(300)
                except Exception:
                    self.status = "Error"
                    logging.exception("Exception while connecting to DX Cluster Provider (" + self._hostname + ").")
                    sleep(5)

            self.status = "Waiting for Data"
            while connected and self._running:
                try:
                    # Check new telnet info against regular expression
                    telnet_output = self._telnet.read_until("\n".encode("latin-1"))
                    match = self._spot_line_pattern.match(telnet_output.decode("latin-1"))
                    if match:
                        spot_time = datetime.strptime(match.group(5), "%H%MZ")
                        spot_datetime = datetime.combine(datetime.now(pytz.UTC).date(), spot_time.time(),
                                                         tzinfo=pytz.UTC)
                        spot = Spot(source=self.name,
                                    dx_call=match.group(3),
                                    de_call=match.group(1),
                                    freq=float(match.group(2)) * 1000,
                                    comment=match.group(4).strip(),
                                    time=spot_datetime.timestamp())

                        # Add to our list
                        self._submit(spot)

                        self.status = "OK"
                        self.last_update_time = datetime.now(pytz.UTC)
                        logging.debug("Data received from DX Cluster " + self._hostname + ".")

                except Exception:
                    connected = False
                    if self._running:
                        self.status = "Error"
                        logging.exception("Exception in DX Cluster Provider (" + self._hostname + ")")
                        sleep(5)
                    else:
                        logging.info("DX Cluster " + self._hostname + " shutting down...")
                        self.status = "Shutting down"

            self.status = "Disconnected"
