import pytest
import asyncio

from async_stream import AsyncIfcImpl, StreamPtr
from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase
from gen3plus.solarman_emu import SolarmanEmu
from infos import Infos, Register

from test_solarman import FakeIfc, FakeInverter, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp
from test_infos_g3p import str_test_ip, bytes_test_ip


pytest_plugins = ('pytest_asyncio',)

timestamp = 0x3224c8bc

class InvStream(MemoryStream):
    def __init__(self, msg=b''):
        super().__init__(msg)

    def _emu_timestamp(self):
        return timestamp

class CldStream(SolarmanEmu):
    def __init__(self, inv: InvStream, inverter=FakeInverter()):
        _ifc = FakeIfc()
        _ifc.remote.stream = inv
        super().__init__(inverter, ('test.local', 1234), _ifc, server_side=False, client_mode=False)
        self.__msg = b''
        self.__msg_len = 0
        self.__offs = 0
        self.msg_count = 0
        self.msg_recvd = []

    def _emu_timestamp(self):
        return timestamp
    
    def append_msg(self, msg):
        self.__msg += msg
        self.__msg_len += len(msg)    

    def _read(self) -> int:
        copied_bytes = 0
        try:    
            if (self.__offs < self.__msg_len):
                self.ifc.rx_fifo += self.__msg[self.__offs:]
                copied_bytes = self.__msg_len - self.__offs
                self.__offs = self.__msg_len
        except Exception:
            pass   # ignore exceptions here
        return copied_bytes

    def _SolarmanBase__flush_recv_msg(self) -> None:
        self.msg_recvd.append(
            {
                'control': self.control,
                'seq': str(self.seq),
                'data_len': self.data_len
            }
        )
        super()._SolarmanBase__flush_recv_msg()
        self.msg_count += 1

@pytest.fixture
def device_ind_msg(bytes_test_ip): # 0x4110
    msg  = b'\xa5\xd4\x00\x10\x41\x00\x01' +get_sn()  +b'\x02\xbc\xc8\x24\x32'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x00\x01\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + bytes_test_ip
    msg += b'\x0f\x00\x01\xb0'
    msg += b'\x02\x0f\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += correct_checksum(msg)
    msg += b'\x15'
    return msg

@pytest.fixture
def inverter_ind_msg():  # 0x4210
    msg  = b'\xa5\x99\x01\x10\x42\x00\x01' +get_sn()  +b'\x01\xb0\x02\xbc\xc8'
    msg += b'\x24\x32\x3c\x00\x00\x00\xa0\x47\xe4\x33\x01\x00\x03\x08\x00\x00'
    msg += b'\x59\x31\x37\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x31'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x40\x10\x08\xc8\x00\x49\x13\x8d\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00'
    msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'
    msg += b'\x04\x00\x00\x01\xff\xff\x00\x01\x00\x06\x00\x68\x00\x68\x05\x00'
    msg += b'\x09\xcd\x07\xb6\x13\x9c\x13\x24\x00\x01\x07\xae\x04\x0f\x00\x41'
    msg += b'\x00\x0f\x0a\x64\x0a\x64\x00\x06\x00\x06\x09\xf6\x12\x8c\x12\x8c'
    msg += b'\x00\x10\x00\x10\x14\x52\x14\x52\x00\x10\x00\x10\x01\x51\x00\x05'
    msg += b'\x00\x00\x00\x01\x13\x9c\x0f\xa0\x00\x4e\x00\x66\x03\xe8\x04\x00'
    msg += b'\x09\xce\x07\xa8\x13\x9c\x13\x26\x00\x00\x00\x00\x00\x00\x00\x00'
    msg += b'\x00\x00\x00\x00\x04\x00\x04\x00\x00\x00\x00\x00\xff\xff\x00\x00'
    msg += b'\x00\x00\x00\x00'
    msg += correct_checksum(msg)
    msg += b'\x15'
    return msg

@pytest.fixture
def inverter_rsp_msg():  # 0x1210
    msg  = b'\xa5\x0a\x00\x10\x12\x02\02' +get_sn()  +b'\x01\x01'
    msg += b'\x00\x00\x00\x00'
    msg += b'\x3c\x00\x00\x00'
    msg += correct_checksum(msg)
    msg += b'\x15'
    return msg

@pytest.fixture
def heartbeat_ind():
    msg  = b'\xa5\x01\x00\x10G\x00\x01\x00\x00\x00\x00\x00Y\x15'
    return msg

@pytest.mark.asyncio
async def test_emu_init_close(my_loop, config_tsun_inv1):
    _ = config_tsun_inv1
    assert asyncio.get_running_loop()
    inv = InvStream()
    cld = CldStream(inv)
    cld.close()


@pytest.mark.asyncio
async def test_emu_start(my_loop, config_tsun_inv1, msg_modbus_rsp, str_test_ip, device_ind_msg):
    _ = config_tsun_inv1
    assert asyncio.get_running_loop()
    inv = InvStream(msg_modbus_rsp)

    assert asyncio.get_running_loop() == inv.mb_timer.loop
    await inv.send_start_cmd(get_sn_int(), str_test_ip, True, inv.mb_first_timeout)
    inv.read()         # read complete msg, and dispatch msg
    assert not inv.header_valid  # must be invalid, since msg was handled and buffer flushed
    assert inv.msg_count == 1
    assert inv.control == 0x1510

    cld = CldStream(inv)
    cld.ifc.update_header_cb(inv.ifc.fwd_fifo.peek())
    assert inv.ifc.fwd_fifo.peek() == device_ind_msg
    cld.close()

@pytest.mark.asyncio
async def test_snd_hb(my_loop, config_tsun_inv1, heartbeat_ind):
    _ = config_tsun_inv1
    inv = InvStream()
    cld = CldStream(inv)

    # await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
    cld.send_heartbeat_cb(0)
    assert cld.ifc.tx_fifo.peek() == heartbeat_ind
    cld.close()

@pytest.mark.asyncio
async def test_snd_inv_data(my_loop, config_tsun_inv1, inverter_ind_msg, inverter_rsp_msg):
    _ = config_tsun_inv1
    inv = InvStream()
    inv.db.set_db_def_value(Register.INVERTER_STATUS, 1)
    inv.db.set_db_def_value(Register.DETECT_STATUS_1, 2)
    inv.db.set_db_def_value(Register.VERSION, 'V4.0.10')
    inv.db.set_db_def_value(Register.GRID_VOLTAGE, 224.8)
    inv.db.set_db_def_value(Register.GRID_CURRENT, 0.73)
    inv.db.set_db_def_value(Register.GRID_FREQUENCY, 50.05)
    inv.db.set_db_def_value(Register.PROD_COMPL_TYPE, 6)
    assert asyncio.get_running_loop() == inv.mb_timer.loop
    await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
    inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17)  # set test value

    cld = CldStream(inv)
    cld.time_ofs = 0x33e447a0
    cld.last_sync = cld._emu_timestamp() - 60
    cld.pkt_cnt = 0x802
    assert cld.data_up_inv == 17  # check test value
    cld.data_up_inv = 0.1         # speedup test first data msg
    cld._init_new_client_conn()
    cld.data_up_inv = 0.5         # timeout for second data msg
    await asyncio.sleep(0.2)
    assert cld.ifc.tx_fifo.get() == inverter_ind_msg

    cld.append_msg(inverter_rsp_msg)
    cld.read()         # read complete msg, and dispatch msg

    assert not cld.header_valid  # must be invalid, since msg was handled and buffer flushed
    assert cld.msg_count == 1
    assert cld.header_len==11
    assert cld.snr == 2070233889
    assert cld.unique_id == '2070233889'
    assert cld.msg_recvd[0]['control']==0x1210
    assert cld.msg_recvd[0]['seq']=='02:02'
    assert cld.msg_recvd[0]['data_len']==0x0a
    assert '02b0' == cld.db.get_db_value(Register.SENSOR_LIST, None)
    assert cld.db.stat['proxy']['Unknown_Msg'] == 0

    cld.close()

@pytest.mark.asyncio
async def test_rcv_invalid(my_loop, config_tsun_inv1, inverter_ind_msg, inverter_rsp_msg):
    _ = config_tsun_inv1
    inv = InvStream()
    assert asyncio.get_running_loop() == inv.mb_timer.loop
    await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
    inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17)  # set test value

    cld = CldStream(inv)
    cld._init_new_client_conn()

    cld.append_msg(inverter_ind_msg)
    cld.read()         # read complete msg, and dispatch msg

    assert not cld.header_valid  # must be invalid, since msg was handled and buffer flushed
    assert cld.msg_count == 1
    assert cld.header_len==11
    assert cld.snr == 2070233889
    assert cld.unique_id == '2070233889'
    assert cld.msg_recvd[0]['control']==0x4210
    assert cld.msg_recvd[0]['seq']=='00:01'
    assert cld.msg_recvd[0]['data_len']==0x199
    assert '02b0' == cld.db.get_db_value(Register.SENSOR_LIST, None)
    assert cld.db.stat['proxy']['Unknown_Msg'] == 1


    cld.close()
