import time
import json
import datetime
try:
    import inverterd
except:
    pass

from typing import Optional
from .._module import MqttModule
from .._node import MqttNode
from .._payload import MqttPayload, bit_field
try:
    from homekit.database import InverterDatabase
except:
    pass

_mult_10 = lambda n: int(n*10)
_div_10 = lambda n: n/10


MODULE_NAME = 'MqttInverterModule'

STATUS_TOPIC = 'status'
GENERATION_TOPIC = 'generation'


class MqttInverterStatusPayload(MqttPayload):
    # 46 bytes
    FORMAT = 'IHHHHHHBHHHHHBHHHHHHHH'

    PACKER = {
        'grid_voltage': _mult_10,
        'grid_freq': _mult_10,
        'ac_output_voltage': _mult_10,
        'ac_output_freq': _mult_10,
        'battery_voltage': _mult_10,
        'battery_voltage_scc': _mult_10,
        'battery_voltage_scc2': _mult_10,
        'pv1_input_voltage': _mult_10,
        'pv2_input_voltage': _mult_10
    }
    UNPACKER = {
        'grid_voltage': _div_10,
        'grid_freq': _div_10,
        'ac_output_voltage': _div_10,
        'ac_output_freq': _div_10,
        'battery_voltage': _div_10,
        'battery_voltage_scc': _div_10,
        'battery_voltage_scc2': _div_10,
        'pv1_input_voltage': _div_10,
        'pv2_input_voltage': _div_10
    }

    time: int
    grid_voltage: float
    grid_freq: float
    ac_output_voltage: float
    ac_output_freq: float
    ac_output_apparent_power: int
    ac_output_active_power: int
    output_load_percent: int
    battery_voltage: float
    battery_voltage_scc: float
    battery_voltage_scc2: float
    battery_discharge_current: int
    battery_charge_current: int
    battery_capacity: int
    inverter_heat_sink_temp: int
    mppt1_charger_temp: int
    mppt2_charger_temp: int
    pv1_input_power: int
    pv2_input_power: int
    pv1_input_voltage: float
    pv2_input_voltage: float

    # H
    mppt1_charger_status: bit_field(0, 16, 2)
    mppt2_charger_status: bit_field(0, 16, 2)
    battery_power_direction: bit_field(0, 16, 2)
    dc_ac_power_direction: bit_field(0, 16, 2)
    line_power_direction: bit_field(0, 16, 2)
    load_connected: bit_field(0, 16, 1)


class MqttInverterGenerationPayload(MqttPayload):
    # 8 bytes
    FORMAT = 'II'

    time: int
    wh: int


class MqttInverterModule(MqttModule):
    _status_poll_freq: int
    _generation_poll_freq: int
    _inverter: Optional[inverterd.Client]
    _database: Optional[InverterDatabase]
    _gen_prev: float

    def __init__(self, status_poll_freq=0, generation_poll_freq=0):
        super().__init__(tick_interval=status_poll_freq)
        self._status_poll_freq = status_poll_freq
        self._generation_poll_freq = generation_poll_freq

        # this defines whether this is a publisher or a subscriber
        if status_poll_freq > 0:
            self._inverter = inverterd.Client()
            self._inverter.connect()
            self._inverter.format(inverterd.Format.SIMPLE_JSON)
            self._database = None
        else:
            self._inverter = None
            self._database = InverterDatabase()

        self._gen_prev = 0

    def on_connect(self, mqtt: MqttNode):
        super().on_connect(mqtt)
        if not self._inverter:
            mqtt.subscribe_module(STATUS_TOPIC, self)
            mqtt.subscribe_module(GENERATION_TOPIC, self)

    def tick(self):
        if not self._inverter:
            return

        # read status
        now = time.time()
        try:
            raw = self._inverter.exec('get-status')
        except inverterd.InverterError as e:
            self._logger.error(f'inverter error: {str(e)}')
            # TODO send to server
            return

        data = json.loads(raw)['data']
        status = MqttInverterStatusPayload(time=round(now), **data)
        self._mqtt_node_ref.publish(STATUS_TOPIC, status.pack())

        # read today's generation stat
        now = time.time()
        if self._gen_prev == 0 or now - self._gen_prev >= self._generation_poll_freq:
            self._gen_prev = now
            today = datetime.date.today()
            try:
                raw = self._inverter.exec('get-day-generated', (today.year, today.month, today.day))
            except inverterd.InverterError as e:
                self._logger.error(f'inverter error: {str(e)}')
                # TODO send to server
                return

            data = json.loads(raw)['data']
            gen = MqttInverterGenerationPayload(time=round(now), wh=data['wh'])
            self._mqtt_node_ref.publish(GENERATION_TOPIC, gen.pack())

    def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]:
        home_id = 1  # legacy compat

        if topic == STATUS_TOPIC:
            s = MqttInverterStatusPayload.unpack(payload)
            self._database.add_status(home_id=home_id,
                                      client_time=s.time,
                                      grid_voltage=int(s.grid_voltage*10),
                                      grid_freq=int(s.grid_freq * 10),
                                      ac_output_voltage=int(s.ac_output_voltage * 10),
                                      ac_output_freq=int(s.ac_output_freq * 10),
                                      ac_output_apparent_power=s.ac_output_apparent_power,
                                      ac_output_active_power=s.ac_output_active_power,
                                      output_load_percent=s.output_load_percent,
                                      battery_voltage=int(s.battery_voltage * 10),
                                      battery_voltage_scc=int(s.battery_voltage_scc * 10),
                                      battery_voltage_scc2=int(s.battery_voltage_scc2 * 10),
                                      battery_discharge_current=s.battery_discharge_current,
                                      battery_charge_current=s.battery_charge_current,
                                      battery_capacity=s.battery_capacity,
                                      inverter_heat_sink_temp=s.inverter_heat_sink_temp,
                                      mppt1_charger_temp=s.mppt1_charger_temp,
                                      mppt2_charger_temp=s.mppt2_charger_temp,
                                      pv1_input_power=s.pv1_input_power,
                                      pv2_input_power=s.pv2_input_power,
                                      pv1_input_voltage=int(s.pv1_input_voltage * 10),
                                      pv2_input_voltage=int(s.pv2_input_voltage * 10),
                                      mppt1_charger_status=s.mppt1_charger_status,
                                      mppt2_charger_status=s.mppt2_charger_status,
                                      battery_power_direction=s.battery_power_direction,
                                      dc_ac_power_direction=s.dc_ac_power_direction,
                                      line_power_direction=s.line_power_direction,
                                      load_connected=s.load_connected)
            return s

        elif topic == GENERATION_TOPIC:
            gen = MqttInverterGenerationPayload.unpack(payload)
            self._database.add_generation(home_id, gen.time, gen.wh)
            return gen