#!/usr/bin/python3
import logging
import unittest
from staslib import conf, ctrl, timeparse, trid
from pyfakefs.fake_filesystem_unittest import TestCase


class TestController(ctrl.Controller):
    def _find_existing_connection(self):
        pass

    def _on_aen(self, aen: int):
        pass

    def _on_nvme_event(self, nvme_event):
        pass

    def reload_hdlr(self):
        pass


class TestDc(ctrl.Dc):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._connected = True

        class Ctrl:
            def __init__(this):
                this.name = 'nvme666'

            def connected(this):
                return self._connected

            def disconnect(this):
                pass

        self._ctrl = Ctrl()

    def _find_existing_connection(self):
        pass

    def _on_aen(self, aen: int):
        pass

    def _on_nvme_event(self, nvme_event):
        pass

    def reload_hdlr(self):
        pass

    def set_connected(self, value):
        self._connected = value

    def connected(self):
        return self._connected


class TestStaf:
    def is_avahi_reported(self, tid):
        return False

    def controller_unresponsive(self, tid):
        pass

    @property
    def tron(self):
        return True


stafd_conf_1 = '''
[Global]
tron=false
hdr-digest=false
data-digest=false
kato=30
queue-size=128
reconnect-delay=10
ctrl-loss-tmo=600
disable-sqflow=false
ignore-iface=false
ip-family=ipv4+ipv6
pleo=enabled

[Service Discovery]
zeroconf=enabled

[Discovery controller connection management]
persistent-connections=true
zeroconf-connections-persistence=10 seconds
'''

stafd_conf_2 = '''
[Discovery controller connection management]
zeroconf-connections-persistence=-1
'''


class Test(TestCase):
    '''Unit tests for class Controller'''

    def setUp(self):
        self.setUpPyfakefs()

        self.fs.create_file(
            '/etc/nvme/hostnqn', contents='nqn.2014-08.org.nvmexpress:uuid:01234567-0123-0123-0123-0123456789ab\n'
        )
        self.fs.create_file('/etc/nvme/hostid', contents='01234567-89ab-cdef-0123-456789abcdef\n')
        self.fs.create_file(
            '/dev/nvme-fabrics',
            contents='instance=-1,cntlid=-1,transport=%s,traddr=%s,trsvcid=%s,nqn=%s,queue_size=%d,nr_io_queues=%d,reconnect_delay=%d,ctrl_loss_tmo=%d,keep_alive_tmo=%d,hostnqn=%s,host_traddr=%s,host_iface=%s,hostid=%s,disable_sqflow,hdr_digest,data_digest,nr_write_queues=%d,nr_poll_queues=%d,tos=%d,fast_io_fail_tmo=%d,discovery,dhchap_secret=%s,dhchap_ctrl_secret=%s\n',
        )

        self.NVME_TID = trid.TID(
            {
                'transport': 'tcp',
                'traddr': '10.10.10.10',
                'subsysnqn': 'nqn.1988-11.com.dell:SFSS:2:20220208134025e8',
                'trsvcid': '8009',
                'host-traddr': '1.2.3.4',
                'host-iface': 'wlp0s20f3',
                'host-nqn': 'nqn.1988-11.com.dell:poweredge:1234',
            }
        )

        default_conf = {
            ('Global', 'tron'): False,
            ('Global', 'hdr-digest'): False,
            ('Global', 'data-digest'): False,
            ('Global', 'kato'): None,  # None to let the driver decide the default
            ('Global', 'queue-size'): None,  # None to let the driver decide the default
            ('Global', 'reconnect-delay'): None,  # None to let the driver decide the default
            ('Global', 'ctrl-loss-tmo'): None,  # None to let the driver decide the default
            ('Global', 'disable-sqflow'): None,  # None to let the driver decide the default
            ('Global', 'persistent-connections'): True,
            ('Discovery controller connection management', 'persistent-connections'): True,
            ('Discovery controller connection management', 'zeroconf-connections-persistence'): timeparse.timeparse(
                '72hours'
            ),
            ('Global', 'ignore-iface'): False,
            ('Global', 'ip-family'): (4, 6),
            ('Global', 'pleo'): True,
            ('Service Discovery', 'zeroconf'): True,
            ('Controllers', 'controller'): list(),
            ('Controllers', 'exclude'): list(),
        }

        self.stafd_conf_file1 = '/etc/stas/stafd1.conf'
        self.fs.create_file(self.stafd_conf_file1, contents=stafd_conf_1)

        self.stafd_conf_file2 = '/etc/stas/stafd2.conf'
        self.fs.create_file(self.stafd_conf_file2, contents=stafd_conf_2)

        self.svcconf = conf.SvcConf(default_conf=default_conf)
        self.svcconf.set_conf_file(self.stafd_conf_file1)

    def tearDown(self):
        pass

    def test_cannot_instantiate_concrete_classes_if_abstract_method_are_not_implemented(self):
        # Make sure we can't instantiate the ABC directly (Abstract Base Class).
        class Controller(ctrl.Controller):
            pass

        self.assertRaises(TypeError, lambda: ctrl.Controller(tid=self.NVME_TID))

    def test_get_device(self):
        controller = TestController(tid=self.NVME_TID, service=TestStaf())
        self.assertEqual(controller._connect_attempts, 0)
        controller._try_to_connect()
        self.assertEqual(controller._connect_attempts, 1)
        self.assertEqual(
            controller.id, "(tcp, 10.10.10.10, 8009, nqn.1988-11.com.dell:SFSS:2:20220208134025e8, wlp0s20f3, 1.2.3.4)"
        )
        # raise Exception(controller._connect_op)
        self.assertEqual(
            str(controller.tid),
            "(tcp, 10.10.10.10, 8009, nqn.1988-11.com.dell:SFSS:2:20220208134025e8, wlp0s20f3, 1.2.3.4)",
        )
        self.assertEqual(controller.device, 'nvme?')
        self.assertEqual(
            controller.controller_id_dict(),
            {
                'transport': 'tcp',
                'traddr': '10.10.10.10',
                'trsvcid': '8009',
                'host-traddr': '1.2.3.4',
                'host-iface': 'wlp0s20f3',
                'subsysnqn': 'nqn.1988-11.com.dell:SFSS:2:20220208134025e8',
                'device': 'nvme?',
                'host-nqn': 'nqn.1988-11.com.dell:poweredge:1234',
            },
        )

        self.assertEqual(
            controller.info(),
            {
                'transport': 'tcp',
                'traddr': '10.10.10.10',
                'subsysnqn': 'nqn.1988-11.com.dell:SFSS:2:20220208134025e8',
                'trsvcid': '8009',
                'host-traddr': '1.2.3.4',
                'host-iface': 'wlp0s20f3',
                'host-nqn': 'nqn.1988-11.com.dell:poweredge:1234',
                'device': 'nvme?',
                'connect attempts': '1',
                'retry connect timer': '60.0s [off]',
                'connect operation': "{'fail count': 0, 'completed': False, 'alive': True}",
            },
        )
        self.assertEqual(
            controller.details(),
            {
                'dctype': '',
                'cntrltype': '',
                'connected': 'False',
                'transport': 'tcp',
                'traddr': '10.10.10.10',
                'trsvcid': '8009',
                'host-traddr': '1.2.3.4',
                'host-iface': 'wlp0s20f3',
                'host-nqn': 'nqn.1988-11.com.dell:poweredge:1234',
                'subsysnqn': 'nqn.1988-11.com.dell:SFSS:2:20220208134025e8',
                'device': 'nvme?',
                'connect attempts': '1',
                'retry connect timer': '60.0s [off]',
                'hostid': '',
                'hostnqn': '',
                'model': '',
                'serial': '',
                'connect operation': "{'fail count': 0, 'completed': False, 'alive': True}",
            },
        )

        # print(controller._connect_op)
        self.assertEqual(controller.cancel(), None)
        self.assertEqual(controller.kill(), None)
        self.assertIsNone(controller.disconnect(lambda *args: None, True))

    def test_connect(self):
        controller = TestController(tid=self.NVME_TID, service=TestStaf())
        self.assertEqual(controller._connect_attempts, 0)
        controller._find_existing_connection = lambda: None
        with self.assertLogs(logger=logging.getLogger(), level='DEBUG') as captured:
            controller._try_to_connect()
        self.assertTrue(len(captured.records) > 0)
        self.assertTrue(
            captured.records[0]
            .getMessage()
            .startswith(
                "Controller._do_connect()           - (tcp, 10.10.10.10, 8009, nqn.1988-11.com.dell:SFSS:2:20220208134025e8, wlp0s20f3, 1.2.3.4) Connecting to nvme control with cfg={"
            )
        )
        self.assertEqual(controller._connect_attempts, 1)

    def test_dlp_supp_opts_as_string(self):
        dlp_supp_opts = 0x7
        opts = ctrl.dlp_supp_opts_as_string(dlp_supp_opts)
        self.assertEqual(['EXTDLPES', 'PLEOS', 'ALLSUBES'], opts)

    def test_ncc(self):
        dlpe = {'eflags': '4'}
        ncc = ctrl.get_ncc(ctrl.get_eflags(dlpe))
        self.assertTrue(ncc)

        dlpe = {}
        ncc = ctrl.get_ncc(ctrl.get_eflags(dlpe))
        self.assertFalse(ncc)

    def test_dc(self):
        self.svcconf.set_conf_file(self.stafd_conf_file1)

        controller = TestDc(TestStaf(), tid=self.NVME_TID)
        controller.set_connected(True)
        controller.origin = 'discovered'

        with self.assertLogs(logger=logging.getLogger(), level='DEBUG') as captured:
            controller.origin = 'blah'
            self.assertEqual(len(captured.records), 1)
            self.assertNotEqual(-1, captured.records[0].getMessage().find("Trying to set invalid origin to blah"))

        controller.set_connected(False)
        with self.assertLogs(logger=logging.getLogger(), level='DEBUG') as captured:
            controller.origin = 'discovered'
            self.assertEqual(len(captured.records), 1)
            self.assertNotEqual(
                -1, captured.records[0].getMessage().find("Controller is not responding. Will be removed by")
            )

        self.svcconf.set_conf_file(self.stafd_conf_file2)
        with self.assertLogs(logger=logging.getLogger(), level='DEBUG') as captured:
            controller.origin = 'discovered'
            self.assertEqual(len(captured.records), 1)
            self.assertNotEqual(-1, captured.records[0].getMessage().find("Controller not responding. Retrying..."))

        controller.set_connected(True)
        with self.assertLogs(logger=logging.getLogger(), level='DEBUG') as captured:
            controller.disconnect(lambda *args: None, keep_connection=False)
            self.assertEqual(len(captured.records), 2)
            self.assertNotEqual(-1, captured.records[0].getMessage().find("nvme666: keep_connection=False"))
            self.assertNotEqual(-1, captured.records[1].getMessage().find("nvme666 - Disconnect initiated"))

    # def test_disconnect(self):


if __name__ == '__main__':
    unittest.main()
