import datetime
import json
import math
import re
import subprocess as sp
import time
from binascii import unhexlify
from multiprocessing import Queue

import serial
from loguru import logger
from typing_extensions import Literal

from homesolar.utils import bluetooth

BYTE_ORDER = Literal["little", "big"]


class Parameter:
    def __init__(self, field: str, start_byte: int, stop_byte: int, byte_order: BYTE_ORDER = "big",
                 signed: bool = False, precision: float = 1.0):
        self.field = field
        self.start_byte = start_byte
        self.stop_byte = stop_byte
        self.byte_order = byte_order
        self.signed = signed
        self.precision = precision

    def get_value(self, reading_data):
        data = reading_data[self.start_byte: self.stop_byte]
        value = int.from_bytes(data[self.start_byte: self.stop_byte], byteorder=self.byte_order,
                               signed=self.signed) * self.precision
        if self.precision < 1:
            value = round(value, math.ceil(math.log(1 / self.precision, 10)))

        return value

    def get_bits(self, reading_data):
        string_bytes = reading_data[self.start_byte: self.stop_byte]

        bytes_list = []
        for i in range(len(string_bytes)):
            bytes_list.append(string_bytes[i:i + 1])

        bits = []
        for byte in bytes_list:
            for i in range(8):
                bits.append((ord(byte) >> i) & 1)
        return bits


class BluetoothClient:
    def __init__(self, address: str, baudrate: int = 9600, parity: str = serial.PARITY_NONE,
                 stopbits: int = serial.STOPBITS_ONE, bytesize: int = serial.EIGHTBITS, params: list[Parameter] = None,
                 request_code: str = "", read_size: int = 0, read_period: int = 5):

        self.address = address
        self.baudrate = baudrate
        self.parity = parity
        self.stopbits = stopbits
        self.bytesize = bytesize
        self.params = params
        self.request_code = request_code
        self.read_size = read_size
        self.read_period = read_period

        self._verification = None
        self._calculated_data = None

        self.connection = serial.Serial()
        self.connect()

    @property
    def verification(self):
        return self._verification

    @verification.setter
    def verification(self, func):
        self._verification = func

    @property
    def calculated_data(self):
        return self._calculated_data

    @calculated_data.setter
    def calculated_data(self, func):
        self._calculated_data = func

    def set_params(self, params: list):
        self.params = params

    def set_request_code(self, request_code: str):
        self.request_code = request_code

    def set_read_size(self, read_size: int):
        self.read_size = read_size

    def set_read_period(self, read_period: int):
        self.read_period = read_period

    def set_verification(self, verification: dict):
        self.verification = verification

    def connect(self):
        if not self.is_connected():
            while self.rfcomm_port() == -1:
                if not self.bind_rfcomm():
                    logger.warning(
                        "Cannot bind Bluetooth Device to a RFComm Address. Please make sure that the device is already paired")
                    break

            self.connection = serial.Serial(
                port=self.rfcomm_port(),
                baudrate=self.baudrate,
                parity=self.parity,
                stopbits=self.stopbits,
                bytesize=self.bytesize)

            if not self.connection.isOpen():
                self.connection.open()

    def disconnect(self):
        if self.connection.isOpen():
            self.connection.close()

        if not self.release_rfcomm():
            logger.warning(
                "Cannot release Bluetooth Device from the RFComm Address. Might want to manually verify")

    def rfcomm_port(self):
        stdoutdata = sp.getoutput("rfcomm")

        pattern = re.compile(r"rfcomm\d+: (\S+) channel (\d+) connected \[tty-attached]")

        # Split the output into lines and search for the target MAC address
        for line in stdoutdata.split('\n'):
            match = pattern.search(line)
            if match:
                mac_address, channel = match.groups()
                if mac_address == self.address:
                    return channel - 1

        return -1

    def bind_rfcomm(self):
        for port_number in range(0, 30):
            stdoutdata = sp.getoutput(f"sudo rfcomm bind dev/rcomm{port_number} {self.address}")
            if "Can't create device" not in stdoutdata.split():
                return True
        return False

    def release_rfcomm(self):
        if self.rfcomm_port() != -1:
            stdoutdata = sp.getoutput(f"sudo rfcomm release dev/rcomm{self.rfcomm_port()}")

            if "Can't release device" not in stdoutdata.split():
                return True
        return False

    def is_connected(self):
        stdoutdata = sp.getoutput("hcitool con")

        if self.address in stdoutdata.split():
            return True
        return False

    def run(self, task_queue: Queue):
        try:
            if self.read_size == 0 or self.params is None:
                raise Exception("Bluetooth read_size is 0, please make sure to setup the device correctly")

            while True:
                if not self.is_connected():
                    self.connect()

                if not self.connection.isOpen():
                    self.connection.open()
                else:
                    try:
                        if self.request_code != "":
                            self.connection.write(unhexlify(self.request_code))

                        read_data = self.connection.read(self.read_size)

                        fields = {}
                        verified = True
                        for param in self.params:
                            fields[param.field] = param.get_value(read_data)

                            if self.calculated_data is not None:
                                self.calculated_data(param, read_data, fields)

                            if self.verification is not None:
                                if not self.verification(param, read_data):
                                    verified = False
                                    self.connection.close()
                                    time.sleep(1)
                                    break

                        if verified:
                            data = {
                                "name": "Antw33-BMS",
                                "payload": json.dumps(fields),
                                'time': datetime.datetime.now().timestamp()
                            }
                            logger.debug(f"Incoming Sensor Data [{data['name']}]")
                            task = {
                                "name": "write_sensor_data",
                                "data": data
                            }
                            task_queue.put(task)
                            time.sleep(5)

                    except Exception as e:
                        logger.warning(f"Something failing during bluetooth communication, [{e}]")

        except Exception as e:
            logger.exception(f"Something went wrong when dealing with bluetooth connection, [{e}]")
        finally:
            self.disconnect()


def initialize(main_task_queue: Queue):
    params = [
        Parameter("Voltage", 4, 6, signed=True, precision=0.1),
        Parameter("Current", 70, 74, signed=True, precision=0.1),
        Parameter("Power", 111, 115, signed=True),
        Parameter("Capacity", 75, 79, signed=False, precision=0.000001),
        Parameter("ChargeStatus", 103, 104),
        Parameter("DischargeStatus", 104, 105),
        Parameter("BalanceStatus", 105, 106),
        Parameter("BalanceCells", 133, 136),
        Parameter("Temp1", 91, 93),
        Parameter("Temp2", 93, 95),
        Parameter("Temp3", 95, 97),
        Parameter("Temp4", 97, 99),
        Parameter("Temp5", 99, 101),
        Parameter("Temp6", 101, 103),
        Parameter("MinCell", 118, 119),
        Parameter("MaxCell", 115, 116),
        Parameter("MinVolt", 119, 121, signed=False, precision=0.001),
        Parameter("MaxVolt", 116, 118, signed=False, precision=0.001),
        Parameter("AvgVolt", 121, 123, precision=0.001),
        Parameter("Cell1", 6, 8, signed=False, precision=0.001),
        Parameter("Cell2", 8, 10, signed=False, precision=0.001),
        Parameter("Cell3", 10, 12, signed=False, precision=0.001),
        Parameter("Cell4", 12, 14, signed=False, precision=0.001),
        Parameter("Cell5", 14, 16, signed=False, precision=0.001),
        Parameter("Cell6", 16, 18, signed=False, precision=0.001),
        Parameter("Cell7", 18, 20, signed=False, precision=0.001),
        Parameter("Cell8", 20, 22, signed=False, precision=0.001),
        Parameter("Cell9", 22, 24, signed=False, precision=0.001),
        Parameter("Cell10", 24, 26, signed=False, precision=0.001),
        Parameter("Cell11", 26, 28, signed=False, precision=0.001),
        Parameter("Cell12", 28, 30, signed=False, precision=0.001),
        Parameter("Cell13", 30, 32, signed=False, precision=0.001),
        Parameter("Cell14", 32, 34, signed=False, precision=0.001),
        Parameter("Cell15", 34, 36, signed=False, precision=0.001),
        Parameter("Cell16", 36, 38, signed=False, precision=0.001),
        Parameter("Cell17", 38, 40, signed=False, precision=0.001),
        Parameter("Cell18", 40, 42, signed=False, precision=0.001),
        Parameter("Cell19", 42, 44, signed=False, precision=0.001),
        Parameter("Cell20", 44, 46, signed=False, precision=0.001),
        Parameter("Cell21", 46, 48, signed=False, precision=0.001),
        Parameter("Cell22", 48, 50, signed=False, precision=0.001),
        Parameter("Cell23", 50, 52, signed=False, precision=0.001),
        Parameter("Cell24", 52, 54, signed=False, precision=0.001)
    ]

    ant_bms = BluetoothClient("AA:BB:CC:91:23:45")
    ant_bms.set_params(params)
    ant_bms.set_read_size(140)
    ant_bms.set_request_code("DBDB00000000")

    def verification(param: Parameter, reading_data):
        if param.field == "AvgVolt":
            if param.get_value(reading_data) == 0:
                return False
        return True

    def calculated_data(param: Parameter, reading_data, fields: dict):
        if param.field == 'BalanceCells':
            for index, bal in enumerate(param.get_bits(reading_data)):
                fields[f"Bal{index}"] = bal

        if param.field == 'AvgVolt':
            fields['SoC'] = bluetooth.ManualSoC().get_soc(param.get_value(reading_data))

    ant_bms.verification = verification
    ant_bms.calculated_data = calculated_data
    ant_bms.run(main_task_queue)
    # try:
    #     ser = serial.Serial(
    #         port=config.homesolar_config['BLUETOOTH']['port'],
    #         baudrate=9600,
    #         parity=serial.PARITY_NONE,
    #         stopbits=serial.STOPBITS_ONE,
    #         bytesize=serial.EIGHTBITS)
    #
    #     try:
    #         ser.open()
    #         time.sleep(1)
    #     except Exception as e:
    #         ser.close()
    #         ser.open()
    #         logger.warning(f"Something went wrong when trying to open Bluetooth as a Serial Connection [{e}]")
    #
    #     first_boot = True
    #     while True:
    #         try:
    #             ser.write(unhexlify(config.homesolar_config['BLUETOOTH']['request_code']))
    #             if first_boot:
    #                 time.sleep(1)
    #                 first_boot = False
    #
    #             antw33 = ser.read(140)
    #
    #             voltage = 0
    #             fields = {}
    #             for data in params:
    #                 fields[data.field] = data.value
    #
    #                 if data.field == 'AvgVolt':
    #                     voltage = data.value
    #
    #                 if data.field == 'BalanceCells':
    #                     for index, bal in enumerate(data.get_bits()):
    #                         fields[f"Bal{index}"] = bal
    #
    #                 if data.field == 'AvgVolt':
    #                     fields['SoC'] = bluetooth.ManualSoC().get_soc(data.value)
    #
    #             if voltage == 0:
    #                 ser.close()
    #                 time.sleep(1)
    #                 ser.open()
    #                 time.sleep(1)
    #
    #             data = {
    #                 "name": "Antw33-BMS",
    #                 "payload": json.dumps(fields),
    #                 'time': datetime.datetime.now().timestamp()
    #             }
    #             logger.debug(f"Incoming Sensor Data [{data['name']}]")
    #             task = {
    #                 "name": "write_sensor_data",
    #                 "data": data
    #             }
    #             main_task_queue.put(task)
    #             time.sleep(5)
    #         except:
    #             ser.close()
    #             time.sleep(1)
    #             ser.open()
    #             time.sleep(1)
    #
    # except Exception as ex:
    #     print(ex)
    # finally:
    #     try:
    #         ser.close()
    #     except Exception as e:
    #         logger.warning(f"Something went wrong when trying to close Bluetooth as Serial Connection [{e}]")
