from decimal import Decimal
from datetime import timedelta, datetime, time, tzinfo
from typing import List, Any, Dict

from data_aggregator_sdk.constants.enums import JournalDataType, SensorType
from pytz import timezone
from data_aggregator_sdk.integration_message import IntegrationV0MessageEvent, IntegrationV0MessageData, \
    IntegrationV0MessageConsumption, CounterType, ResourceType, IntegrationV0MessageClock, \
    IntegrationV0MessageGeneration, IntegrationV0MessageSensor, IntegrationV0MessageConnectionQuality

from data_gateway_sdk.utils.buf_ref import BufRef
from data_gateway_sdk.utils.packet import Packet
from data_gateway_sdk.utils.timestamp_calculation import timestamp_calculation


# PACKET (128 bits)   smpm_ul_device_water_meter_nbiot_16b_daily
#
# RESULT int:        141810285579170350578080716311712763013
# RESULT bin:  MSB   01101010 10101111 10101010 11100100 00000110 10110001 11101010 11100100 10010000 10111010 01111111 11111111 11111111 11000000 00001100 10000101   LSB
# RESULT hex:  LE    85 0c c0 ff ff 7f ba 90 e4 ea b1 06 e4 aa af 6a
#
# name                      type       size  value(int)                                                                                                                        data(bits)
# ------------------------  ---------  ----  ----------  --------------------------------------------------------------------------------------------------------------------------------
# packet_type_id.0.VAL      u7            7           5                                                                                                                           0000101
# packet_type_id.0.DFF      bool          1           1                                                                                                                          1
# packet_type_id.1.VAL      u2            2           0                                                                                                                        00
# packet_type_id.1.DFF      bool          1           1                                                                                                                       1
# packet_type_id.2.VAL      u2            2           1                                                                                                                     01
# packet_type_id.2.DFF      bool          1           0                                                                                                                    0
# days_ago                  timedelta     5           0                                                                                                               00000
# sync_time_days_ago        timedelta     3           0                                                                                                            000
# timestamp_s               timedelta    26    33554431                                                                                  01111111111111111111111111
# temperature               u7            7          58                                                                           0111010
# battery_volts             uf6p1         6          33                                                                     100001
# event_reset               bool          1           0                                                                    0
# event_low_battery_level   bool          1           0                                                                   0
# event_temperature_limits  bool          1           1                                                                  1
# direct_flow_volume        uf32p3       32   112323300                                  00000110101100011110101011100100
# reverse_flow_volume       uf12p3       12        2788                      101011100100
# event_magnet              bool          1           0                     0
# rsrp                      u7            7         125              1111101
# rsrq                      u4            4          10          1010
# snr                       u6            6          42    101010
# ecl                       u2            2           1  01


class SmpmUlDeviceWaterMeterNbiot16BDailyData(Packet):
    days_ago: timedelta
    sync_time_days_ago: timedelta
    timestamp_s: timedelta
    temperature: int
    battery_volts: float
    event_reset: bool
    event_low_battery_level: bool
    event_temperature_limits: bool
    direct_flow_volume: float
    reverse_flow_volume: float
    event_magnet: bool
    rsrp: int
    rsrq: int
    snr: int
    ecl: int

    def serialize(self) -> bytes:
        data = self
        result = 0
        size = 0
        result |= ((5) & (2 ** (7) - 1)) << size
        size += 7
        result |= ((1) & (2 ** (1) - 1)) << size
        size += 1
        result |= ((0) & (2 ** (2) - 1)) << size
        size += 2
        result |= ((1) & (2 ** (1) - 1)) << size
        size += 1
        result |= ((1) & (2 ** (2) - 1)) << size
        size += 2
        result |= ((0) & (2 ** (1) - 1)) << size
        size += 1
        isinstance(data.days_ago, (int, timedelta))
        days_ago_tmp1 = int(
            data.days_ago.total_seconds() // 86400 if isinstance(data.days_ago, timedelta) else data.days_ago // 86400)
        assert 0 <= days_ago_tmp1 <= 31
        result |= ((days_ago_tmp1) & (2 ** (5) - 1)) << size
        size += 5
        isinstance(data.sync_time_days_ago, (int, timedelta))
        result |= ((max(min(7,
                            int(data.sync_time_days_ago.total_seconds() // 86400 if isinstance(data.sync_time_days_ago,
                                                                                               timedelta) else data.sync_time_days_ago // 86400)),
                        0)) & (2 ** (3) - 1)) << size
        size += 3
        isinstance(data.timestamp_s, (int, timedelta))
        value_int_tmp2 = int(data.timestamp_s.total_seconds() // 1 if isinstance(data.timestamp_s,
                                                                                 timedelta) else data.timestamp_s // 1) & 67108863
        result |= ((value_int_tmp2) & (2 ** (26) - 1)) << size
        size += 26
        assert isinstance(data.temperature, int)
        assert -35 <= data.temperature <= 92
        result |= (((data.temperature + 35)) & (2 ** (7) - 1)) << size
        size += 7
        assert isinstance(data.battery_volts, (int, float))
        assert 0.0 <= data.battery_volts <= 6.3
        result |= ((int(round(float(data.battery_volts) * 10.0, 0))) & (2 ** (6) - 1)) << size
        size += 6
        assert isinstance(data.event_reset, bool)
        result |= ((int(data.event_reset)) & (2 ** (1) - 1)) << size
        size += 1
        assert isinstance(data.event_low_battery_level, bool)
        result |= ((int(data.event_low_battery_level)) & (2 ** (1) - 1)) << size
        size += 1
        assert isinstance(data.event_temperature_limits, bool)
        result |= ((int(data.event_temperature_limits)) & (2 ** (1) - 1)) << size
        size += 1
        assert isinstance(data.direct_flow_volume, (int, float))
        result |= ((int(round(float(data.direct_flow_volume) * 1000.0, 0)) & 4294967295) & (2 ** (32) - 1)) << size
        size += 32
        assert isinstance(data.reverse_flow_volume, (int, float))
        result |= ((int(round(float(data.reverse_flow_volume) * 1000.0, 0)) & 4095) & (2 ** (12) - 1)) << size
        size += 12
        assert isinstance(data.event_magnet, bool)
        result |= ((int(data.event_magnet)) & (2 ** (1) - 1)) << size
        size += 1
        assert isinstance(data.rsrp, int)
        assert -140 <= data.rsrp <= -13
        result |= (((data.rsrp + 140)) & (2 ** (7) - 1)) << size
        size += 7
        assert isinstance(data.rsrq, int)
        assert -19 <= data.rsrq <= -4
        result |= (((data.rsrq + 19)) & (2 ** (4) - 1)) << size
        size += 4
        assert isinstance(data.snr, int)
        assert -20 <= data.snr <= 43
        result |= (((data.snr + 20)) & (2 ** (6) - 1)) << size
        size += 6
        assert isinstance(data.ecl, int)
        assert 0 <= data.ecl <= 3
        result |= ((data.ecl) & (2 ** (2) - 1)) << size
        size += 2
        return result.to_bytes(16, "little")

    @classmethod
    def parse(cls, buf: BufRef) -> 'SmpmUlDeviceWaterMeterNbiot16BDailyData':
        result__el_tmp3: Dict[str, Any] = dict()
        if 5 != buf.shift(7):
            raise ValueError("packet_type_id: buffer doesn't match value")
        if 1 != buf.shift(1):
            raise ValueError("packet_type_id: buffer doesn't match value")
        if 0 != buf.shift(2):
            raise ValueError("packet_type_id: buffer doesn't match value")
        if 1 != buf.shift(1):
            raise ValueError("packet_type_id: buffer doesn't match value")
        if 1 != buf.shift(2):
            raise ValueError("packet_type_id: buffer doesn't match value")
        if 0 != buf.shift(1):
            raise ValueError("packet_type_id: buffer doesn't match value")
        result__el_tmp3["days_ago"] = timedelta(seconds=buf.shift(5) * 86400)
        result__el_tmp3["sync_time_days_ago"] = timedelta(seconds=buf.shift(3) * 86400)
        result__el_tmp3["timestamp_s"] = timedelta(seconds=buf.shift(26) * 1)
        result__el_tmp3["temperature"] = buf.shift(7) + -35
        result__el_tmp3["battery_volts"] = round(buf.shift(6) / 10.0, 1)
        result__el_tmp3["event_reset"] = bool(buf.shift(1))
        result__el_tmp3["event_low_battery_level"] = bool(buf.shift(1))
        result__el_tmp3["event_temperature_limits"] = bool(buf.shift(1))
        result__el_tmp3["direct_flow_volume"] = round(buf.shift(32) / 1000.0, 3)
        result__el_tmp3["reverse_flow_volume"] = round(buf.shift(12) / 1000.0, 3)
        result__el_tmp3["event_magnet"] = bool(buf.shift(1))
        result__el_tmp3["rsrp"] = buf.shift(7) + -140
        result__el_tmp3["rsrq"] = buf.shift(4) + -19
        result__el_tmp3["snr"] = buf.shift(6) + -20
        result__el_tmp3["ecl"] = buf.shift(2) + 0
        result = SmpmUlDeviceWaterMeterNbiot16BDailyData(**result__el_tmp3)
        return result

    def to_integration_data(self, received_at: datetime, device_tz: tzinfo, **kwargs: Any) -> List[IntegrationV0MessageData]:
        return [
            IntegrationV0MessageData(
                dt=datetime.combine(
                    (received_at.astimezone(device_tz) - self.days_ago).date(),
                    time(hour=0, minute=0, second=0),
                    device_tz,
                ).astimezone(timezone('UTC')) + timedelta(days=1) if self.days_ago.total_seconds() else received_at,
                sensors=[
                    IntegrationV0MessageSensor(
                        value=Decimal(str(self.battery_volts)),
                        sensor_type=SensorType.BATTERY,
                    ),
                    IntegrationV0MessageSensor(
                        value=Decimal(str(self.temperature)),
                        sensor_type=SensorType.TEMPERATURE,
                    ),
                ],
                consumption=[
                    IntegrationV0MessageConsumption(
                        counter_type=CounterType.COMMON,
                        resource_type=ResourceType.COMMON,
                        channel=1,
                        value=Decimal(str(self.direct_flow_volume)),
                        overloading_value=Decimal(str(4294967.295)),
                        journal_data_type=JournalDataType.END_OF_DAY if self.days_ago else JournalDataType.CURRENT,
                    ),
                ],
                events=[
                    *([IntegrationV0MessageEvent.RESET] if self.event_reset else []),
                    *([IntegrationV0MessageEvent.BATTERY_IS_LOW] if self.event_low_battery_level else []),
                    *([IntegrationV0MessageEvent.TEMPERATURE_LIMIT] if self.event_temperature_limits else []),
                    *([IntegrationV0MessageEvent.MAGNET_WAS_DETECTED] if self.event_magnet else []),
                ],
                generation=[
                    IntegrationV0MessageGeneration(
                        counter_type=CounterType.COMMON,
                        resource_type=ResourceType.COMMON,
                        value=Decimal(str(self.reverse_flow_volume)),
                        overloading_value=Decimal(str(40.95)),
                        journal_data_type=JournalDataType.END_OF_DAY if self.days_ago else JournalDataType.CURRENT,
                    ),
                ],
                clock=[IntegrationV0MessageClock(value=timestamp_calculation(received_at, self.timestamp_s, timedelta(seconds=67108863)))] if self.timestamp_s else [],
                connection_quality=IntegrationV0MessageConnectionQuality(
                    rsrp=self.rsrp,
                    rsrq=self.rsrq,
                    snr=self.snr,
                    ecl=self.ecl,
                )
            ),
        ]
