from pathlib import Path
from tempfile import TemporaryFile
from xml.etree import ElementTree

import pytest

from core.emulator.data import IpPrefixes, LinkOptions
from core.emulator.enumerations import EventTypes
from core.emulator.session import Session
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode, WlanNode
from core.services.utility import SshService


class TestXml:
    def test_xml_hooks(self, session: Session, tmpdir: TemporaryFile):
        """
        Test save/load hooks in xml.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        """
        # create hooks
        file_name = "runtime_hook.sh"
        data = "#!/bin/sh\necho hello"
        state = EventTypes.RUNTIME_STATE
        session.add_hook(state, file_name, data)

        file_name = "instantiation_hook.sh"
        data = "#!/bin/sh\necho hello"
        state = EventTypes.INSTANTIATION_STATE
        session.add_hook(state, file_name, data)

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        runtime_hooks = session.hooks.get(state)
        assert runtime_hooks
        runtime_hook = runtime_hooks[0]
        assert file_name == runtime_hook[0]
        assert data == runtime_hook[1]

    def test_xml_ptp(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for a ptp network.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create nodes
        node1 = session.add_node(CoreNode)
        node2 = session.add_node(CoreNode)

        # link nodes
        iface1_data = ip_prefixes.create_iface(node1)
        iface2_data = ip_prefixes.create_iface(node2)
        session.add_link(node1.id, node2.id, iface1_data, iface2_data)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(node2.id, CoreNode)
        # verify no links are known
        assert len(session.link_manager.links()) == 0

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(node2.id, CoreNode)
        assert len(session.link_manager.links()) == 1

    def test_xml_ptp_services(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for a ptp neetwork.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create nodes
        node1 = session.add_node(CoreNode)
        node2 = session.add_node(CoreNode)

        # link nodes to ptp net
        iface1_data = ip_prefixes.create_iface(node1)
        iface2_data = ip_prefixes.create_iface(node2)
        session.add_link(node1.id, node2.id, iface1_data, iface2_data)

        # set custom values for node service
        session.services.set_service(node1.id, SshService.name)
        service_file = SshService.configs[0]
        file_data = "# test"
        session.services.set_service_file(
            node1.id, SshService.name, service_file, file_data
        )

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(node2.id, CoreNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # retrieve custom service
        service = session.services.get_service(node1.id, SshService.name)

        # verify nodes have been recreated
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(node2.id, CoreNode)
        assert service.config_data.get(service_file) == file_data

    def test_xml_mobility(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for mobility.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create wlan
        wlan = session.add_node(WlanNode)
        session.mobility.set_model(wlan, BasicRangeModel, {"test": "1"})

        # create nodes
        options = CoreNode.create_options()
        options.model = "mdr"
        node1 = session.add_node(CoreNode, options=options)
        node2 = session.add_node(CoreNode, options=options)

        # link nodes
        for node in [node1, node2]:
            iface_data = ip_prefixes.create_iface(node)
            session.add_link(node.id, wlan.id, iface1_data=iface_data)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(node2.id, CoreNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # retrieve configuration we set originally
        value = str(session.mobility.get_config("test", wlan.id, BasicRangeModel.name))

        # verify nodes and configuration were restored
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(node2.id, CoreNode)
        assert session.get_node(wlan.id, WlanNode)
        assert value == "1"

    def test_network_to_network(self, session: Session, tmpdir: TemporaryFile):
        """
        Test xml generation when dealing with network to network nodes.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        """
        # create nodes
        switch1 = session.add_node(SwitchNode)
        switch2 = session.add_node(SwitchNode)

        # link nodes
        session.add_link(switch1.id, switch2.id)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(switch1.id, SwitchNode)
        with pytest.raises(CoreError):
            assert not session.get_node(switch2.id, SwitchNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        switch1 = session.get_node(switch1.id, SwitchNode)
        switch2 = session.get_node(switch2.id, SwitchNode)
        assert switch1
        assert switch2
        assert len(session.link_manager.links()) == 1

    def test_link_options(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for a ptp network.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create nodes
        node1 = session.add_node(CoreNode)
        iface1_data = ip_prefixes.create_iface(node1)
        switch = session.add_node(SwitchNode)

        # create link
        options = LinkOptions()
        options.loss = 10.5
        options.bandwidth = 50000
        options.jitter = 10
        options.delay = 30
        options.dup = 5
        options.buffer = 100
        session.add_link(node1.id, switch.id, iface1_data, options=options)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(switch.id, SwitchNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(switch.id, SwitchNode)
        assert len(session.link_manager.links()) == 1
        link = list(session.link_manager.links())[0]
        link_options = link.options()
        assert options.loss == link_options.loss
        assert options.bandwidth == link_options.bandwidth
        assert options.jitter == link_options.jitter
        assert options.delay == link_options.delay
        assert options.dup == link_options.dup
        assert options.buffer == link_options.buffer

    def test_link_options_ptp(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for a ptp network.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create nodes
        node1 = session.add_node(CoreNode)
        iface1_data = ip_prefixes.create_iface(node1)
        node2 = session.add_node(CoreNode)
        iface2_data = ip_prefixes.create_iface(node2)

        # create link
        options = LinkOptions()
        options.loss = 10.5
        options.bandwidth = 50000
        options.jitter = 10
        options.delay = 30
        options.dup = 5
        options.buffer = 100
        session.add_link(node1.id, node2.id, iface1_data, iface2_data, options)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(node2.id, CoreNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(node2.id, CoreNode)
        assert len(session.link_manager.links()) == 1
        link = list(session.link_manager.links())[0]
        link_options = link.options()
        assert options.loss == link_options.loss
        assert options.bandwidth == link_options.bandwidth
        assert options.jitter == link_options.jitter
        assert options.delay == link_options.delay
        assert options.dup == link_options.dup
        assert options.buffer == link_options.buffer

    def test_link_options_bidirectional(
        self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
    ):
        """
        Test xml client methods for a ptp network.

        :param session: session for test
        :param tmpdir: tmpdir to create data in
        :param ip_prefixes: generates ip addresses for nodes
        """
        # create nodes
        node1 = session.add_node(CoreNode)
        iface1_data = ip_prefixes.create_iface(node1)
        node2 = session.add_node(CoreNode)
        iface2_data = ip_prefixes.create_iface(node2)

        # create link
        options1 = LinkOptions()
        options1.unidirectional = 1
        options1.bandwidth = 5000
        options1.delay = 10
        options1.loss = 10.5
        options1.dup = 5
        options1.jitter = 5
        options1.buffer = 50
        iface1, iface2 = session.add_link(
            node1.id, node2.id, iface1_data, iface2_data, options1
        )
        options2 = LinkOptions()
        options2.unidirectional = 1
        options2.bandwidth = 10000
        options2.delay = 20
        options2.loss = 10
        options2.dup = 10
        options2.jitter = 10
        options2.buffer = 100
        session.update_link(node2.id, node1.id, iface2.id, iface1.id, options2)

        # instantiate session
        session.instantiate()

        # save xml
        xml_file = tmpdir.join("session.xml")
        file_path = Path(xml_file.strpath)
        session.save_xml(file_path)

        # verify xml file was created and can be parsed
        assert xml_file.isfile()
        assert ElementTree.parse(file_path)

        # stop current session, clearing data
        session.shutdown()

        # verify nodes have been removed from session
        with pytest.raises(CoreError):
            assert not session.get_node(node1.id, CoreNode)
        with pytest.raises(CoreError):
            assert not session.get_node(node2.id, CoreNode)

        # load saved xml
        session.open_xml(file_path, start=True)

        # verify nodes have been recreated
        assert session.get_node(node1.id, CoreNode)
        assert session.get_node(node2.id, CoreNode)
        assert len(session.link_manager.links()) == 1
        assert options1.bandwidth == iface1.options.bandwidth
        assert options1.delay == iface1.options.delay
        assert options1.loss == iface1.options.loss
        assert options1.dup == iface1.options.dup
        assert options1.jitter == iface1.options.jitter
        assert options1.buffer == iface1.options.buffer
        assert options2.bandwidth == iface2.options.bandwidth
        assert options2.delay == iface2.options.delay
        assert options2.loss == iface2.options.loss
        assert options2.dup == iface2.options.dup
        assert options2.jitter == iface2.options.jitter
        assert options2.buffer == iface2.options.buffer