#
#
#

from unittest import TestCase

from helpers import SimpleProvider

from octodns.record import Record
from octodns.record.exception import ValidationError
from octodns.record.rr import RrParseError
from octodns.record.urlfwd import UrlfwdRecord, UrlfwdValue
from octodns.zone import Zone


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

    def test_urlfwd(self):
        a_values = [
            UrlfwdValue(
                {
                    'path': '/',
                    'target': 'http://foo',
                    'code': 301,
                    'masking': 2,
                    'query': 0,
                }
            ),
            UrlfwdValue(
                {
                    'path': '/target',
                    'target': 'http://target',
                    'code': 302,
                    'masking': 2,
                    'query': 0,
                }
            ),
        ]
        a_data = {'ttl': 30, 'values': a_values}
        a = UrlfwdRecord(self.zone, 'a', a_data)
        self.assertEqual('a', a.name)
        self.assertEqual('a.unit.tests.', a.fqdn)
        self.assertEqual(30, a.ttl)
        self.assertEqual(a_values[0]['path'], a.values[0].path)
        self.assertEqual(a_values[0]['target'], a.values[0].target)
        self.assertEqual(a_values[0]['code'], a.values[0].code)
        self.assertEqual(a_values[0]['masking'], a.values[0].masking)
        self.assertEqual(a_values[0]['query'], a.values[0].query)
        self.assertEqual(a_values[1]['path'], a.values[1].path)
        self.assertEqual(a_values[1]['target'], a.values[1].target)
        self.assertEqual(a_values[1]['code'], a.values[1].code)
        self.assertEqual(a_values[1]['masking'], a.values[1].masking)
        self.assertEqual(a_values[1]['query'], a.values[1].query)
        self.assertEqual(a_data, a.data)

        b_value = UrlfwdValue(
            {
                'path': '/',
                'target': 'http://location',
                'code': 301,
                'masking': 2,
                'query': 0,
            }
        )
        b_data = {'ttl': 30, 'value': b_value}
        b = UrlfwdRecord(self.zone, 'b', b_data)
        self.assertEqual(b_value['path'], b.values[0].path)
        self.assertEqual(b_value['target'], b.values[0].target)
        self.assertEqual(b_value['code'], b.values[0].code)
        self.assertEqual(b_value['masking'], b.values[0].masking)
        self.assertEqual(b_value['query'], b.values[0].query)
        self.assertEqual(b_data, b.data)

        target = SimpleProvider()
        # No changes with self
        self.assertFalse(a.changes(a, target))
        # Diff in path causes change
        other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].path = '/change'
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)
        # Diff in target causes change
        other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].target = 'http://target'
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)
        # Diff in code causes change
        other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].code = 302
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)
        # Diff in masking causes change
        other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].masking = 0
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)
        # Diff in query causes change
        other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
        other.values[0].query = 1
        change = a.changes(other, target)
        self.assertEqual(change.existing, a)
        self.assertEqual(change.new, other)

        # hash
        v = UrlfwdValue(
            {
                'path': '/',
                'target': 'http://place',
                'code': 301,
                'masking': 2,
                'query': 0,
            }
        )
        o = UrlfwdValue(
            {
                'path': '/location',
                'target': 'http://redirect',
                'code': 302,
                'masking': 2,
                'query': 0,
            }
        )
        values = set()
        values.add(v)
        self.assertTrue(v in values)
        self.assertFalse(o in values)
        values.add(o)
        self.assertTrue(o in values)

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

    def test_validation(self):
        # doesn't blow up
        Record.new(
            self.zone,
            '',
            {
                'type': 'URLFWD',
                'ttl': 600,
                'value': {
                    'path': '/',
                    'target': 'http://foo',
                    'code': 301,
                    'masking': 2,
                    'query': 0,
                },
            },
        )
        Record.new(
            self.zone,
            '',
            {
                'type': 'URLFWD',
                'ttl': 600,
                'values': [
                    {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 2,
                        'query': 0,
                    },
                    {
                        'path': '/target',
                        'target': 'http://target',
                        'code': 302,
                        'masking': 2,
                        'query': 0,
                    },
                ],
            },
        )

        # missing path
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 2,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(['missing path'], ctx.exception.reasons)

        # missing target
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'code': 301,
                        'masking': 2,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(['missing target'], ctx.exception.reasons)

        # missing code
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'masking': 2,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(['missing code'], ctx.exception.reasons)

        # invalid code
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 'nope',
                        'masking': 2,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(['invalid return code "nope"'], ctx.exception.reasons)

        # unrecognized code
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 3,
                        'masking': 2,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(
            ['unrecognized return code "3"'], ctx.exception.reasons
        )

        # missing masking
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(['missing masking'], ctx.exception.reasons)

        # invalid masking
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 'nope',
                        'query': 0,
                    },
                },
            )
        self.assertEqual(
            ['invalid masking setting "nope"'], ctx.exception.reasons
        )

        # unrecognized masking
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 3,
                        'query': 0,
                    },
                },
            )
        self.assertEqual(
            ['unrecognized masking setting "3"'], ctx.exception.reasons
        )

        # missing query
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 2,
                    },
                },
            )
        self.assertEqual(['missing query'], ctx.exception.reasons)

        # invalid query
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 2,
                        'query': 'nope',
                    },
                },
            )
        self.assertEqual(
            ['invalid query setting "nope"'], ctx.exception.reasons
        )

        # unrecognized query
        with self.assertRaises(ValidationError) as ctx:
            Record.new(
                self.zone,
                '',
                {
                    'type': 'URLFWD',
                    'ttl': 600,
                    'value': {
                        'path': '/',
                        'target': 'http://foo',
                        'code': 301,
                        'masking': 2,
                        'query': 3,
                    },
                },
            )
        self.assertEqual(
            ['unrecognized query setting "3"'], ctx.exception.reasons
        )

    def test_urlfwd_value_rdata_text(self):
        # empty string won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('')

        # single word won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('nope')

        # 2nd word won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('1 2')

        # 3rd word won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('1 2 3')

        # 4th word won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('1 2 3 4')

        # 6th word won't parse
        with self.assertRaises(RrParseError):
            UrlfwdValue.parse_rdata_text('1 2 3 4 5 6')

        # code, masking, and query not ints
        self.assertEqual(
            {
                'path': 'one',
                'target': 'urlfwd.unit.tests.',
                'code': 'one',
                'masking': 'two',
                'query': 'three',
            },
            UrlfwdValue.parse_rdata_text(
                'one urlfwd.unit.tests. one two three'
            ),
        )

        # valid
        self.assertEqual(
            {
                'path': 'one',
                'target': 'urlfwd.unit.tests.',
                'code': 1,
                'masking': 2,
                'query': 3,
            },
            UrlfwdValue.parse_rdata_text('one urlfwd.unit.tests. 1 2 3'),
        )

        # quoted
        self.assertEqual(
            {
                'path': 'one',
                'target': 'urlfwd.unit.tests.',
                'code': 1,
                'masking': 2,
                'query': 3,
            },
            UrlfwdValue.parse_rdata_text('"one" "urlfwd.unit.tests." 1 2 3'),
        )

        zone = Zone('unit.tests.', [])
        a = UrlfwdRecord(
            zone,
            'urlfwd',
            {
                'ttl': 32,
                'value': {
                    'path': 'one',
                    'target': 'urlfwd.unit.tests.',
                    'code': 1,
                    'masking': 2,
                    'query': 3,
                },
            },
        )
        self.assertEqual('one', a.values[0].path)
        self.assertEqual('urlfwd.unit.tests.', a.values[0].target)
        self.assertEqual(1, a.values[0].code)
        self.assertEqual(2, a.values[0].masking)
        self.assertEqual(3, a.values[0].query)

        # both directions should match
        rdata = '"one" "urlfwd.unit.tests." 1 2 3'
        record = UrlfwdRecord(
            zone,
            'urlfwd',
            {'ttl': 32, 'value': UrlfwdValue.parse_rdata_text(rdata)},
        )
        self.assertEqual(rdata, record.values[0].rdata_text)


class TestUrlfwdValue(TestCase):

    def test_template(self):
        value = UrlfwdValue(
            {
                'path': '/',
                'target': 'http://foo',
                'code': 301,
                'masking': 2,
                'query': 0,
            }
        )
        got = value.template({'needle': 42})
        self.assertIs(value, got)

        value = UrlfwdValue(
            {
                'path': '/{needle}',
                'target': 'http://foo.{needle}',
                'code': 301,
                'masking': 2,
                'query': 0,
            }
        )
        got = value.template({'needle': 42})
        self.assertIsNot(value, got)
        self.assertEqual('/42', got.path)
        self.assertEqual('http://foo.42', got.target)
