class TestImmutableDict(TestCase):
    def setUp(self) -> None:
        self.initial_dict = {
            't': 10,
            'a': [{
                'b': 1,
                'c': 2
            }],
            'd': {
                'e': {
                    'f': True
                }
            }
        }
        self.immutable_dict = ImmutableCaseInsensitiveDict(self.initial_dict)

    def test_assignment_not_allowed(self) -> None:
        with self.assertRaises(TypeError):
            # pylint: disable=E1137
            self.immutable_dict['a'] = 1  # type: ignore

    def test_nested_assignment_not_allowed(self) -> None:
        with self.assertRaises(TypeError):
            # pylint: disable=E1137
            self.immutable_dict['d']['e']['f'] = False

    def test_original_dict_not_mutated(self) -> None:
        _ = self.immutable_dict['a']
        self.assertEqual(self.initial_dict, self.immutable_dict._container)

    def test_raises_error_for_non_existent_key(self) -> None:
        with self.assertRaises(KeyError):
            _ = self.immutable_dict['a-non-existent-key']

    def test_getitem(self) -> None:
        self.assertEqual(self.immutable_dict['t'], self.initial_dict['t'])

    def test_nested_access(self) -> None:
        self.assertEqual(self.immutable_dict['a'][0]['b'], 1)
        self.assertEqual(self.immutable_dict['d']['e']['f'], True)

    def test_equality(self) -> None:
        # Equality with dict
        self.assertEqual(self.immutable_dict, self.initial_dict)
        # Equality with another instance
        equal_dict = deepcopy(self.initial_dict)
        other_immutable_dict = ImmutableCaseInsensitiveDict(equal_dict)
        self.assertEqual(other_immutable_dict, self.immutable_dict)

    def test_shallow_copy(self) -> None:
        self.assertEqual(self.immutable_dict._container, self.initial_dict)
        self.assertIsNot(self.immutable_dict._container, self.initial_dict)

    def test_get(self) -> None:
        self.assertIsInstance(self.immutable_dict.get('d'),
                              ImmutableCaseInsensitiveDict)
        self.assertIsInstance(self.immutable_dict.get('a'), ImmutableList)

    def test_ensure_immutable(self) -> None:
        initial_dict = {
            'a': [[1, 2], [3, 4]],
            'b': {
                'c': {
                    'd': 1
                }
            },
            't': 10,
            'e': {
                'f': [{
                    'g': 90
                }]
            }
        }
        immutable_dict = ImmutableCaseInsensitiveDict(initial_dict)
        # List of lists with immutable elements
        self.assertIsInstance(immutable_dict['a'], ImmutableList)
        self.assertIsInstance(immutable_dict['a'][0], ImmutableList)
        self.assertEqual(immutable_dict['a'][0][1], 2)
        # Two-level nested dictionary
        self.assertIsInstance(immutable_dict['b'],
                              ImmutableCaseInsensitiveDict)
        self.assertIsInstance(immutable_dict['b']['c'],
                              ImmutableCaseInsensitiveDict)
        self.assertEqual(immutable_dict['b']['c']['d'], 1)
        # Plain immutable object at top-level
        self.assertIsInstance(immutable_dict['t'], int)
        self.assertEqual(immutable_dict['t'], 10)
        # Two-level dictionary with nested list as value
        self.assertIsInstance(immutable_dict['e']['f'], ImmutableList)
        self.assertIsInstance(immutable_dict['e']['f'][0],
                              ImmutableCaseInsensitiveDict)
        self.assertEqual(immutable_dict['e']['f'][0]['g'], 90)

    def test_copy(self) -> None:
        initial_dict = {
            'a': True,
            'b': {
                'c': {
                    'e': True,
                    'd': [{
                        'g': False
                    }]
                }
            }
        }
        immutable_dict = ImmutableCaseInsensitiveDict(initial_dict)
        dict_copy = immutable_dict.copy()
        self.assertIsNot(dict_copy, initial_dict)
        self.assertIsNot(dict_copy['b'], initial_dict['b'])
        self.assertEqual(dict_copy['b'], initial_dict['b'])
        self.assertIsInstance(dict_copy['b'], dict)
        self.assertIsInstance(dict_copy['b']['c'], dict)
        self.assertIsInstance(dict_copy['b']['c']['d'], list)
        self.assertIsNot(dict_copy['b']['c']['d'],
                         initial_dict['b']['c']['d'])  # type: ignore
        dict_copy['h'] = False
        self.assertIs(dict_copy['h'], False)
class TestCaseInsensitiveLookup(TestCase):
    def setUp(self) -> None:
        self.data = {
            'Content-Encoding': 'gzip',
            'ACCEPT': '*/*',
            'X-Forwarded-For': '10.0.0.1, 10.0.0.2',
            'Request': {
                'HTTP_version': '1.1',
                'query': 'p=1&r=2'
            }
        }
        self.case_insensitive_dict = ImmutableCaseInsensitiveDict(self.data)

    def test_membership_check(self) -> None:
        self.assertIn('accept', self.case_insensitive_dict)
        self.assertIn('Accept', self.case_insensitive_dict)
        self.assertIn('ACCEPT', self.case_insensitive_dict)
        self.assertIn('CONTENT-Encoding', self.case_insensitive_dict)
        self.assertIn('REQUEST', self.case_insensitive_dict)
        self.assertNotIn('Content-Length', self.case_insensitive_dict)
        self.assertIn('http_version', self.case_insensitive_dict['request'])

    def test_immutable_return_value(self) -> None:
        self.assertIsInstance(self.case_insensitive_dict['request'],
                              ImmutableCaseInsensitiveDict)
        self.assertEqual(self.case_insensitive_dict['request'],
                         self.data['Request'])
        self.assertEqual(self.case_insensitive_dict['request']['Query'],
                         'p=1&r=2')

    def test_getitem(self) -> None:
        self.assertEqual(self.case_insensitive_dict['accept'],
                         self.data['ACCEPT'])
        self.assertEqual(self.case_insensitive_dict['CONTENT-ENCODING'],
                         self.data['Content-Encoding'])
        self.assertEqual(self.case_insensitive_dict['request']['http_version'],
                         '1.1')
        with self.assertRaises(KeyError):
            _ = self.case_insensitive_dict['Unknown-key']

    def test_get(self) -> None:
        self.assertEqual(self.case_insensitive_dict.get('x-forwarded-for'),
                         self.data['X-Forwarded-For'])
        self.assertEqual(self.case_insensitive_dict.get('X-FORWARDED-FOR'),
                         self.data['X-Forwarded-For'])
        self.assertIsNone(self.case_insensitive_dict.get('Unknown-key'))
        self.assertEqual(self.case_insensitive_dict.get('Unknown-key', 1), 1)

    def test_original_keys_on_iteration(self) -> None:
        self.assertListEqual(list(self.data), list(self.case_insensitive_dict))
        self.assertListEqual(list(self.data.keys()),
                             list(self.case_insensitive_dict.keys()))
        self.assertListEqual(list(self.data.items()),
                             list(self.case_insensitive_dict.items()))

    def test_on_conflict_first_occurrence_returned(self) -> None:
        data = {
            'X-Forwarded-For': '10.0.0.1, 10.0.0.2',
            'X-FORWARDED-FOR': '10.0.0.3, 10.0.0.4'
        }
        case_insensitive_dict = ImmutableCaseInsensitiveDict(data)
        self.assertEqual(case_insensitive_dict['X-FORWARDED-FOR'],
                         '10.0.0.3, 10.0.0.4')
        self.assertEqual(case_insensitive_dict['x-forwarded-for'],
                         data['X-Forwarded-For'])

    def test_on_empty_container_raises_key_error(self) -> None:
        case_insensitive_dict = ImmutableCaseInsensitiveDict({})
        with self.assertRaisesRegex(KeyError, 'non_existing_key'):
            _ = case_insensitive_dict['non_existing_key']

    def test_incremental_map_build(self) -> None:
        # Normally protected attributes should not participate in testing,
        # however this is critical functionality, so we maximize the test coverage.
        self.assertIn('CONTENT-ENCODING', self.case_insensitive_dict)
        self.assertEqual(self.case_insensitive_dict._case_insensitive_keymap,
                         {'content-encoding': 'Content-Encoding'})
        self.assertIn('X-FORWARDED-FOR', self.case_insensitive_dict)
        self.assertEqual(
            self.case_insensitive_dict._case_insensitive_keymap, {
                'content-encoding': 'Content-Encoding',
                'accept': 'ACCEPT',
                'x-forwarded-for': 'X-Forwarded-For'
            })
        # Ensure existing keys in the key map are returned
        self.assertIn('CONTENT-ENCODING', self.case_insensitive_dict)
        self.assertIn('REQUEST', self.case_insensitive_dict)
        self.assertEqual(
            self.case_insensitive_dict._case_insensitive_keymap, {
                'content-encoding': 'Content-Encoding',
                'accept': 'ACCEPT',
                'x-forwarded-for': 'X-Forwarded-For',
                'request': 'Request'
            })
        # Ensure after the keymap is fully built keys are successfully matched
        self.assertIn('X-FORWARDED-FOR', self.case_insensitive_dict)
        self.assertTrue(self.case_insensitive_dict._keymap_fully_built)