#
#
#

from unittest import TestCase

from helpers import SimpleProvider

from octodns.record import Record
from octodns.record.exception import ValidationError
from octodns.record.naptr import NaptrRecord, NaptrValue
from octodns.record.rr import RrParseError
from octodns.zone import Zone


class TestRecordNaptr(TestCase):
    zone = Zone('unit.tests.', [])

    def test_naptr(self):
        a_values = [
            NaptrValue(
                {
                    'order': 10,
                    'preference': 11,
                    'flags': 'X',
                    'service': 'Y',
                    'regexp': 'Z',
                    'replacement': '.',
                }
            ),
            NaptrValue(
                {
                    'order': 20,
                    'preference': 21,
                    'flags': 'A',
                    'service': 'B',
                    'regexp': 'C',
                    'replacement': 'foo.com',
                }
            ),
        ]
        a_data = {'ttl': 30, 'values': a_values}
        a = NaptrRecord(self.zone, 'a', a_data)
        self.assertEqual('a', a.name)
        self.assertEqual('a.unit.tests.', a.fqdn)
        self.assertEqual(30, a.ttl)
        for i in (0, 1):
            for k in a_values[0].keys():
                self.assertEqual(a_values[i][k], getattr(a.values[i], k))
        self.assertEqual(a_data, a.data)

        b_value = NaptrValue(
            {
                'order': 30,
                'preference': 31,
                'flags': 'M',
                'service': 'N',
                'regexp': 'O',
                'replacement': 'x',
            }
        )
        b_data = {'ttl': 30, 'value': b_value}
        b = NaptrRecord(self.zone, 'b', b_data)
        for k in a_values[0].keys():
            self.assertEqual(b_value[k], getattr(b.values[0], k))
        self.assertEqual(b_data, b.data)

        target = SimpleProvider()
        # No changes with self
        self.assertFalse(a.changes(a, target))
        # Diff in priority causes change
        other = NaptrRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].order = 22
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)
        # Diff in replacement causes change
        other.values[0].order = a.values[0].order
        other.values[0].replacement = 'smtpX'
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)

        # full sorting
        # equivalent
        b_naptr_value = b.values[0]
        self.assertTrue(b_naptr_value == b_naptr_value)
        self.assertFalse(b_naptr_value != b_naptr_value)
        self.assertTrue(b_naptr_value <= b_naptr_value)
        self.assertTrue(b_naptr_value >= b_naptr_value)
        # by order
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 10,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 40,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        # by preference
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 30,
                    'preference': 10,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 30,
                    'preference': 40,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        # by flags
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'A',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'Z',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        # by service
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'A',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'Z',
                    'regexp': 'O',
                    'replacement': 'x',
                }
            )
        )
        # by regexp
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'A',
                    'replacement': 'x',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'Z',
                    'replacement': 'x',
                }
            )
        )
        # by replacement
        self.assertTrue(
            b_naptr_value
            > NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'a',
                }
            )
        )
        self.assertTrue(
            b_naptr_value
            < NaptrValue(
                {
                    'order': 30,
                    'preference': 31,
                    'flags': 'M',
                    'service': 'N',
                    'regexp': 'O',
                    'replacement': 'z',
                }
            )
        )

        # __repr__ doesn't blow up
        a.__repr__()

        # Hash
        v = NaptrValue(
            {
                'order': 30,
                'preference': 31,
                'flags': 'M',
                'service': 'N',
                'regexp': 'O',
                'replacement': 'z',
            }
        )
        o = NaptrValue(
            {
                'order': 30,
                'preference': 32,
                'flags': 'M',
                'service': 'N',
                'regexp': 'O',
                'replacement': 'z',
            }
        )
        values = set()
        values.add(v)
        self.assertTrue(v in values)
        self.assertFalse(o in values)
        values.add(o)
        self.assertTrue(o in values)

        self.assertEqual(30, o.order)
        o.order = o.order + 1
        self.assertEqual(31, o.order)

        self.assertEqual(32, o.preference)
        o.preference = o.preference + 1
        self.assertEqual(33, o.preference)

        self.assertEqual('M', o.flags)
        o.flags = 'P'
        self.assertEqual('P', o.flags)

        self.assertEqual('N', o.service)
        o.service = 'Q'
        self.assertEqual('Q', o.service)

        self.assertEqual('O', o.regexp)
        o.regexp = 'R'
        self.assertEqual('R', o.regexp)

        self.assertEqual('z', o.replacement)
        o.replacement = '1'
        self.assertEqual('1', o.replacement)

    def test_naptr_value_rdata_text(self):
        # things with the wrong number of words won't parse
        for v in (
            '',
            'one',
            'one two',
            'one two three',
            'one two three four',
            'one two three four five',
            'one two three four five six seven',
        ):
            with self.assertRaises(RrParseError):
                NaptrValue.parse_rdata_text(v)

        # we don't care if the types of things are correct when parsing rr text
        self.assertEqual(
            {
                'order': 'one',
                'preference': 'two',
                'flags': 'three',
                'service': 'four',
                'regexp': 'five',
                'replacement': 'six',
            },
            NaptrValue.parse_rdata_text('one two three four five six'),
        )

        # order and preference will be converted to int's when possible
        self.assertEqual(
            {
                'order': 1,
                'preference': 2,
                'flags': 'three',
                'service': 'four',
                'regexp': 'five',
                'replacement': 'six',
            },
            NaptrValue.parse_rdata_text('1 2 three four five six'),
        )

        # string fields are unquoted if needed
        self.assertEqual(
            {
                'order': 1,
                'preference': 2,
                'flags': 'three',
                'service': 'four',
                'regexp': 'five',
                'replacement': 'six',
            },
            NaptrValue.parse_rdata_text('1 2 "three" "four" "five" "six"'),
        )

        # make sure that the cstor is using parse_rdata_text
        zone = Zone('unit.tests.', [])
        a = NaptrRecord(
            zone,
            'naptr',
            {
                'ttl': 32,
                'value': {
                    'order': 1,
                    'preference': 2,
                    'flags': 'S',
                    'service': 'service',
                    'regexp': 'regexp',
                    'replacement': 'replacement',
                },
            },
        )
        self.assertEqual(1, a.values[0].order)
        self.assertEqual(2, a.values[0].preference)
        self.assertEqual('S', a.values[0].flags)
        self.assertEqual('service', a.values[0].service)
        self.assertEqual('regexp', a.values[0].regexp)
        self.assertEqual('replacement', a.values[0].replacement)
        s = '1 2 "S" "service" "regexp" replacement'
        self.assertEqual(s, a.values[0].rdata_text)

    def test_validation(self):
        # doesn't blow up
        Record.new(
            self.zone,
            '',
            {
                'type': 'NAPTR',
                'ttl': 600,
                'value': {
                    'order': 10,
                    'preference': 20,
                    'flags': 'S',
                    'service': 'srv',
                    'regexp': '.*',
                    'replacement': '.',
                },
            },
        )

        # missing X priority
        value = {
            'order': 10,
            'preference': 20,
            'flags': 'S',
            'service': 'srv',
            'regexp': '.*',
            'replacement': '.',
        }
        for k in (
            'order',
            'preference',
            'flags',
            'service',
            'regexp',
            'replacement',
        ):
            v = dict(value)
            del v[k]
            with self.assertRaises(ValidationError) as ctx:
                Record.new(
                    self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v}
                )
            self.assertEqual([f'missing {k}'], ctx.exception.reasons)

        # non-int order
        v = dict(value)
        v['order'] = 'boo'
        with self.assertRaises(ValidationError) as ctx:
            Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v})
        self.assertEqual(['invalid order "boo"'], ctx.exception.reasons)

        # non-int preference
        v = dict(value)
        v['preference'] = 'who'
        with self.assertRaises(ValidationError) as ctx:
            Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v})
        self.assertEqual(['invalid preference "who"'], ctx.exception.reasons)

        # unrecognized flags
        v = dict(value)
        v['flags'] = 'X'
        with self.assertRaises(ValidationError) as ctx:
            Record.new(self.zone, '', {'type': 'NAPTR', 'ttl': 600, 'value': v})
        self.assertEqual(['unrecognized flags "X"'], ctx.exception.reasons)


class TestNaptrValue(TestCase):

    def test_template(self):
        value = NaptrValue(
            {
                'order': 10,
                'preference': 11,
                'flags': 'X',
                'service': 'Y',
                'regexp': 'Z',
                'replacement': '.',
            }
        )
        got = value.template({'needle': 42})
        self.assertIs(value, got)

        value = NaptrValue(
            {
                'order': 10,
                'preference': 11,
                'flags': 'X',
                'service': 'Y{needle}',
                'regexp': 'Z{needle}',
                'replacement': '.{needle}',
            }
        )
        got = value.template({'needle': 42})
        self.assertIsNot(value, got)
        self.assertEqual('Y42', got.service)
        self.assertEqual('Z42', got.regexp)
        self.assertEqual('.42', got.replacement)
