import unittest
from collections import OrderedDict
from aioupnp.serialization.ssdp import SSDPDatagram
from aioupnp.fault import UPnPError
from aioupnp.constants import UPNP_ORG_IGD


class TestSSDPDatagram(unittest.TestCase):
    def test_fail_to_init(self):
        datagram_args = OrderedDict([
            ('Host', "{}:{}".format('239.255.255.250', 1900)),
            ('Man', '"ssdp:discover"'),
            ('ST', 'ssdp:all'),
            ('MX', 5),
        ])

        with self.assertRaises(UPnPError):
            SSDPDatagram("?", datagram_args)

    def test_fail_to_decode_missing_required(self):
        packet = \
        b'M-SEARCH * HTTP/1.1\r\n' \
        b'Host: 239.255.255.250:1900\r\n' \
        b'ST: ssdp:all\r\n' \
        b'MX: 5\r\n' \
        b'\r\n'

        with self.assertRaises(UPnPError):
            SSDPDatagram.decode(packet)

    def test_fail_to_decode_blank(self):
        packet = b''

        with self.assertRaises(UPnPError):
            SSDPDatagram.decode(packet)

    def test_fail_to_decode_one_line(self):
        packet = b'M-SEARCH * HTTP/1.1'

        with self.assertRaises(UPnPError):
            SSDPDatagram.decode(packet)

    def test_cli_args(self):
        datagram_args = OrderedDict([
            ('Host', "{}:{}".format('239.255.255.250', 1900)),
            ('Man', '"ssdp:discover"'),
            ('ST', 'ssdp:all'),
            ('MX', 5),
        ])
        packet = SSDPDatagram("M-SEARCH", datagram_args)
        self.assertEqual(
            packet.get_cli_igd_kwargs(),
            '--Host=239.255.255.250:1900 --Man="ssdp:discover" --ST=ssdp:all --MX=5'
        )

    def test_as_dict(self):
        datagram_args = OrderedDict([
            ('Host', "{}:{}".format('239.255.255.250', 1900)),
            ('Man', '"ssdp:discover"'),
            ('ST', 'ssdp:all'),
            ('MX', 5),
        ])
        packet = SSDPDatagram("M-SEARCH", datagram_args)
        self.assertDictEqual(
            packet.as_dict(),
            {
                "Host": "239.255.255.250:1900",
                "Man": "\"ssdp:discover\"",
                "ST": "ssdp:all",
                "MX": 5
            }
        )


class TestMSearchDatagramSerialization(unittest.TestCase):
    packet = \
        b'M-SEARCH * HTTP/1.1\r\n' \
        b'Host: 239.255.255.250:1900\r\n' \
        b'Man: "ssdp:discover"\r\n' \
        b'ST: ssdp:all\r\n' \
        b'MX: 5\r\n' \
        b'\r\n'

    datagram_args = OrderedDict([
        ('Host', "{}:{}".format('239.255.255.250', 1900)),
        ('Man', '"ssdp:discover"'),
        ('ST', 'ssdp:all'),
        ('MX', 5),
    ])

    def test_deserialize_and_reserialize(self):
        packet1 = SSDPDatagram.decode(self.packet)
        packet2 = SSDPDatagram("M-SEARCH", self.datagram_args)
        self.assertEqual(packet2.encode(), packet1.encode())


class TestSerializationOrder(TestMSearchDatagramSerialization):
    packet = \
        b'M-SEARCH * HTTP/1.1\r\n' \
        b'Host: 239.255.255.250:1900\r\n' \
        b'ST: ssdp:all\r\n' \
        b'Man: "ssdp:discover"\r\n' \
        b'MX: 5\r\n' \
        b'\r\n'

    datagram_args = OrderedDict([
        ('Host', "{}:{}".format('239.255.255.250', 1900)),
        ('ST', 'ssdp:all'),
        ('Man', '"ssdp:discover"'),
        ('MX', 5),
    ])


class TestSerializationPreserveCase(TestMSearchDatagramSerialization):
    packet = \
        b'M-SEARCH * HTTP/1.1\r\n' \
        b'HOST: 239.255.255.250:1900\r\n' \
        b'ST: ssdp:all\r\n' \
        b'Man: "ssdp:discover"\r\n' \
        b'mx: 5\r\n' \
        b'\r\n'

    datagram_args = OrderedDict([
        ('HOST', "{}:{}".format('239.255.255.250', 1900)),
        ('ST', 'ssdp:all'),
        ('Man', '"ssdp:discover"'),
        ('mx', 5),
    ])


class TestSerializationPreserveAllLowerCase(TestMSearchDatagramSerialization):
    packet = \
        b'M-SEARCH * HTTP/1.1\r\n' \
        b'host: 239.255.255.250:1900\r\n' \
        b'st: ssdp:all\r\n' \
        b'man: "ssdp:discover"\r\n' \
        b'mx: 5\r\n' \
        b'\r\n'

    datagram_args = OrderedDict([
        ('host', "{}:{}".format('239.255.255.250', 1900)),
        ('st', 'ssdp:all'),
        ('man', '"ssdp:discover"'),
        ('mx', 5),
    ])


class TestParseMSearchRequestWithQuotes(unittest.TestCase):
    datagram = b"M-SEARCH * HTTP/1.1\r\n" \
               b"HOST: 239.255.255.250:1900\r\n" \
               b"ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" \
               b"MAN: \"ssdp:discover\"\r\n" \
               b"MX: 1\r\n" \
               b"\r\n"

    def test_parse_m_search(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertTrue(packet._packet_type, packet._M_SEARCH)
        self.assertEqual(packet.host, '239.255.255.250:1900')
        self.assertEqual(packet.st, 'urn:schemas-upnp-org:device:InternetGatewayDevice:1')
        self.assertEqual(packet.man, '"ssdp:discover"')
        self.assertEqual(packet.mx, 1)


class TestParseMSearchRequestWithoutQuotes(unittest.TestCase):
    datagram = b'M-SEARCH * HTTP/1.1\r\n' \
               b'HOST: 239.255.255.250:1900\r\n' \
               b'ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n' \
               b'MAN: ssdp:discover\r\n' \
               b'MX: 1\r\n' \
               b'\r\n'

    def test_parse_m_search(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertTrue(packet._packet_type, packet._M_SEARCH)
        self.assertEqual(packet.host, '239.255.255.250:1900')
        self.assertEqual(packet.st, 'urn:schemas-upnp-org:device:InternetGatewayDevice:1')
        self.assertEqual(packet.man, 'ssdp:discover')
        self.assertEqual(packet.mx, 1)

    def test_serialize_m_search(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertEqual(packet.encode().encode(), self.datagram)


class TestParseMSearchResponse(unittest.TestCase):
    datagram = "\r\n".join([
        'HTTP/1.1 200 OK',
        'CACHE_CONTROL: max-age=1800',
        'LOCATION: http://10.0.0.1:49152/InternetGatewayDevice.xml',
        'SERVER: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'ST: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()

    def test_parse_m_search_response(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertTrue(packet._packet_type, packet._OK)
        self.assertEqual(packet.cache_control, 'max-age=1800')
        self.assertEqual(packet.location, 'http://10.0.0.1:49152/InternetGatewayDevice.xml')
        self.assertEqual(packet.server, 'Linux, UPnP/1.0, DIR-890L Ver 1.20')
        self.assertEqual(packet.st, 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1')
        self.assertEqual(packet.usn, 'uuid:00000000-0000-0000-0000-000000000000::urn:'
                                     'schemas-upnp-org:service:WANCommonInterfaceConfig:1'
)


class TestParseMSearchResponseRedSonic(TestParseMSearchResponse):
    datagram = \
        b"HTTP/1.1 200 OK\r\n" \
        b"CACHE-CONTROL: max-age=1800\r\n" \
        b"DATE: Thu, 04 Oct 2018 22:59:40 GMT\r\n" \
        b"EXT:\r\n" \
        b"LOCATION: http://10.1.10.1:49152/IGDdevicedesc_brlan0.xml\r\n" \
        b"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" \
        b"01-NLS: 00000000-0000-0000-0000-000000000000\r\n" \
        b"SERVER: Linux/3.14.28-Prod_17.2, UPnP/1.0, Portable SDK for UPnP devices/1.6.22\r\n" \
        b"X-User-Agent: redsonic\r\n" \
        b"ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" \
        b"USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" \
        b"\r\n"

    def test_parse_m_search_response(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertTrue(packet._packet_type, packet._OK)
        self.assertEqual(packet.cache_control, 'max-age=1800')
        self.assertEqual(packet.location, 'http://10.1.10.1:49152/IGDdevicedesc_brlan0.xml')
        self.assertEqual(packet.server, 'Linux/3.14.28-Prod_17.2, UPnP/1.0, Portable SDK for UPnP devices/1.6.22')
        self.assertEqual(packet.st, UPNP_ORG_IGD)


class TestParseMSearchResponseDashCacheControl(TestParseMSearchResponse):
    datagram = "\r\n".join([
        'HTTP/1.1 200 OK',
        'CACHE-CONTROL: max-age=1800',
        'LOCATION: http://10.0.0.1:49152/InternetGatewayDevice.xml',
        'SERVER: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'ST: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()


class TestParseMSearchResponseCaseInsensitive(TestParseMSearchResponse):
    datagram = "\r\n".join([
        'HTTP/1.1 200 OK',
        'cache-CONTROL: max-age=1800',
        'LOCATION: http://10.0.0.1:49152/InternetGatewayDevice.xml',
        'Server: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'st: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()

    def test_get_case_insensitive(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertEqual('max-age=1800', packet['Cache_Control'])

    def test_key_error(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertRaises(KeyError, lambda : packet['Cache Control'])


class TestFailToParseMSearchResponseNoST(unittest.TestCase):
    datagram = "\r\n".join([
        'HTTP/1.1 200 OK',
        'CACHE_CONTROL: max-age=1800',
        'LOCATION: http://10.0.0.1:49152/InternetGatewayDevice.xml',
        'SERVER: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()

    def test_fail_to_parse_m_search_response(self):
        self.assertRaises(UPnPError, SSDPDatagram.decode, self.datagram)


class TestFailToParseMSearchResponseNoOK(TestFailToParseMSearchResponseNoST):
    datagram = "\r\n".join([
        'cache-CONTROL: max-age=1800',
        'LOCATION: http://10.0.0.1:49152/InternetGatewayDevice.xml',
        'Server: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'st: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()


class TestFailToParseMSearchResponseNoLocation(TestFailToParseMSearchResponseNoST):
    datagram = "\r\n".join([
        'HTTP/1.1 200 OK',
        'cache-CONTROL: max-age=1800',
        'Server: Linux, UPnP/1.0, DIR-890L Ver 1.20',
        'st: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
        'USN: uuid:00000000-0000-0000-0000-000000000000::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
    ]).encode()


class TestParseNotify(unittest.TestCase):
    datagram = \
        b'NOTIFY * HTTP/1.1 \r\n' \
        b'Host: 239.255.255.250:1900\r\n' \
        b'Cache-Control: max-age=180\r\n' \
        b'Location: http://192.168.1.1:5431/dyndev/uuid:000c-29ea-247500c00068\r\n' \
        b'NT: upnp:rootdevice\r\n' \
        b'NTS: ssdp:alive\r\n' \
        b'SERVER: LINUX/2.4 UPnP/1.0 BRCM400/1.0\r\n' \
        b'USN: uuid:000c-29ea-247500c00068::upnp:rootdevice\r\n' \
        b'\r\n'

    def test_parse_notify(self):
        packet = SSDPDatagram.decode(self.datagram)
        self.assertTrue(packet._packet_type, packet._NOTIFY)
        self.assertEqual(packet.host, '239.255.255.250:1900')
        self.assertEqual(packet.cache_control, 'max-age=180')  # this is an optional field
        self.assertEqual(packet.location, 'http://192.168.1.1:5431/dyndev/uuid:000c-29ea-247500c00068')
        self.assertEqual(packet.nt, 'upnp:rootdevice')
        self.assertEqual(packet.nts, 'ssdp:alive')
        self.assertEqual(packet.server, 'LINUX/2.4 UPnP/1.0 BRCM400/1.0')
        self.assertEqual(packet.usn, 'uuid:000c-29ea-247500c00068::upnp:rootdevice')