"""Code for the paho client to do connection and get data

is based on example from HiveMq


"""

import os
import sys
import datetime
import json

import paho.mqtt.client as paho
from paho import mqtt

from src.in_io import in_io as inio
from src.in_io import sqlite as pysql
import src.setup_db.set_up_log as setin


class Handler:
    """A class to host handlers for the MQTT connection
    Set up to make it easier to provide required information in a 'easy' way

    TODO
    Make function calls more generic when logging,
    so can be to file or database (of different types)
    """

    def __init__(self):
        self.save_dict = {}
        self.conn = None
        self.cursor = None
        self.setup = False

    def on_message(self, client, userdata, msg):
        """
        Prints a mqtt message to stdout ( used as callback for subscribe )

        :param client: the client itself
        :param userdata: userdata is set when initiating the client, here it is userdata=None
        :param msg: the message with topic and payload
        """
        print(msg.topic + " " + str(msg.qos) + " " + str(msg.payload))
        try:
            data = json.loads(msg.payload)
        except json.JSONDecodeError as e:
            print("Not a valid JSON for database")
            return
        ct = datetime.datetime.now()
        ctdata = {"timestamp": int(ct.timestamp()), **data}

        # This saves to SQL
        if "sqlite" in self.save_dict:
            # get devinfo
            tab_known, dev_inf = pysql.get_device_info(
                self.save_dict["sqlite"]["cursor"], data["inator"]
            )
            if tab_known:
                # only attempt to save to SQL if known
                # remove inator as not needed now
                del ctdata["inator"]
                # get the data to id key
                key_dict = pysql.get_data_id_key(
                    self.save_dict["sqlite"]["cursor"], dev_inf, ctdata
                )
                __ = pysql.insert_dev_log_table(
                    self.save_dict["sqlite"]["cursor"], dev_inf, ctdata, key_dict
                )
                self.save_dict["sqlite"]["connection"].commit()

        # This saves to text file
        if "file" in self.save_dict:
            inio.save_plain_text(self.save_dict["file"]["filepath"], ct, data)

    # setting callbacks for different events to see if it works, print the message etc.
    def on_connect(self, client, userdata, flags, rc, properties=None):
        """
        Prints the result of the connection
        with a reasoncode to stdout ( used as callback for connect )

        :param client: the client itself
        :param userdata: userdata is set when initiating the client, here it is userdata=None
        :param flags: these are response flags sent by the broker
        :param rc: stands for reasonCode, which is a code for the connection result
        :param properties: can be used in MQTTv5, but is optional
        """
        print(f"CONNACK received with code {rc}.")
        print("subscribing")
        client.subscribe(userdata["topics"]["topics"], qos=1)

    # with this callback you can see if your publish was successful
    def on_publish(self, client, userdata, mid, properties=None):
        """
        Prints mid to stdout to reassure a successful publish ( used as callback for publish )

        :param client: the client itself
        :param userdata: userdata is set when initiating the client, here it is userdata=None
        :param mid: variable returned from the corresponding publish() call,
        to allow outgoing messages to be tracked
        :param properties: can be used in MQTTv5, but is optional
        """
        print("mid: " + str(mid))

    # print which topic was subscribed to
    def on_subscribe(self, client, userdata, mid, granted_qos, properties=None):
        """
        Prints a reassurance for successfully subscribing

        :param client: the client itself
        :param userdata: userdata is set when initiating the client, here it is userdata=None
        :param mid: variable returned from the corresponding publish() call,
                    to allow outgoing messages to be tracked
        :param granted_qos: this is the qos that you declare when subscribing,
                    use the same one for publishing
        :param properties: can be used in MQTTv5, but is optional
        """
        print("Subscribed: " + str(mid) + " " + str(granted_qos))

    def set_up_sql(self, sql_opts: dict):
        """Function sets up the sql connection

        Parameters
        ----------
        sql_opts : dict
            dictionary of the options needed to setup the dictionary

        Returns
        -------
        dict
            dictionary that contains the connection information to the DB
        """
        if sql_opts["setup"]:
            # User wants to create the DB before monitoring
            print("Running set up of new Inator DB")

            sql_set = setin.set_up_inator_log(sql_opts)
            if sql_set:
                print(f"✅ Inator DB {sql_opts['sql']} is setup and ready to log")
            else:
                print(f"❌ Could not set p Inator DB {sql_opts['sql']}, exiting")
                exit(2)
            # check if db exists

        if not os.path.exists(sql_opts["sql"]):
            print(
                f"❌ The database {sql_opts['sql']} does not exist,"
                "will need to create and add tables"
            )
            print("Rerun script with setup and create flags set")
            sys.exit(2)
        else:
            print(f"Connecting to {sql_opts['sql']}")
            # Connect to existing sql database and get cursor
            dberror, con, cursor = pysql.connect_db(sql_opts["sql"], 0)
            if dberror == 0:
                print(f"✅ Connected to {sql_opts['sql']}")
            else:
                print(f"❌ failed to connect to {sql_opts['sql']}")

        return {"connection": con, "cursor": cursor, "db_path": sql_opts["sql"]}

    def set_up_file(self, fileopts: dict):
        """Function to set up the text file to log to

        Parameters
        ----------
        fileopts : dict
            dictionary of the file options, such as file path

        Returns
        -------
        dict
            dictionary with options needed by handler
        """
        if fileopts["setfile"]:
            print("Will set up header of text file")
            with open(fileopts["file"], "w", encoding="utf-8") as line:
                head = "Timestamp,name,Data...\n"
                line.writelines(head)
        # Check that the file exists
        if not os.path.exists(fileopts["file"]):
            print(
                f"❌ The file {fileopts['file']} does not exist, need to create with headers"
            )
            print("Rerun script with setup flag set")
            sys.exit(2)
        else:
            print(f"✅ The file {fileopts['file']} exsists. Will append")

        return {"filepath": fileopts["file"]}


def client_setup(auth: dict, connect: dict, topics: dict, handler):
    """Setup the MQTT client
    Creates appropriate callback functions and subscribes to the chosen topic

    Parameters
    ----------
    auth : dict
        The authorisations info, name is user name, pass is password for connection
    connect : dict
        The connection information, url is the URL and port is the port, normally 8883 for MQTT
    topics : dict
        The topic to subscribe to

    Returns
    -------
    Paho Client
        The client with connections established
    """
    # using MQTT version 5 here, for 3.1.1: MQTTv311, 3.1: MQTTv31
    # userdata is user defined data of any type, updated by user_data_set()
    # client_id is the given name of the client
    client = paho.Client(
        client_id="", userdata={"topics": topics}, protocol=paho.MQTTv5
    )
    # Set up on connection call back
    client.on_connect = handler.on_connect
    client.on_subscribe = handler.on_subscribe
    client.on_message = handler.on_message
    client.on_publish = handler.on_publish

    # enable TLS for secure connection
    client.tls_set(tls_version=mqtt.client.ssl.PROTOCOL_TLS)
    # set username and password
    client.username_pw_set(auth["name"], auth["pass"])
    # connect to HiveMQ Cloud on port 8883 (default for MQTT)
    try:
        client.connect(connect["url"], connect["port"])
    except TimeoutError:
        print("❌ Error when trying to connect to MQTT broker")
        sys.exit(3)

    # # setting callbacks, use separate functions like above for better visibility
    # client.on_subscribe = handler.on_subscribe
    # client.on_message = handler.on_message
    # client.on_publish = handler.on_publish

    # subscribe to all topics of encyclopedia by using the wildcard "#"
    # client.subscribe(topics["topics"], qos=1)

    return client
