###############################################################################
#
# (C) Copyright 2025 EVERYSK TECHNOLOGIES
#
# This is an unpublished work containing confidential and proprietary
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
#
###############################################################################
# ruff: noqa: E501, ERA001, RUF012

import re
from collections.abc import Iterator
from datetime import date, datetime
from sys import maxsize
from zoneinfo import ZoneInfo

from everysk.core.datetime import Date, DateTime
from everysk.core.exceptions import ReadonlyError, RequiredError
from everysk.core.mapping import fields
from everysk.core.mapping.base import BaseMapping
from everysk.core.unittests import TestCase, mock


class BaseMappingFieldTestCase(TestCase):
    def test_init_field_type(self) -> None:
        field = fields.BaseMappingField(field_type=int)
        self.assertEqual(field.field_type, int)

    def test_init_field_name(self) -> None:
        field = fields.BaseMappingField(field_name='test_field')
        self.assertEqual(field.field_name, 'test_field')

    def test_init_choices(self) -> None:
        field = fields.BaseMappingField()
        self.assertSetEqual(field.choices, set())
        field = fields.BaseMappingField(choices={1, 2, 3})
        self.assertSetEqual(field.choices, {1, 2, 3})

    def test_init_default(self) -> None:
        field = fields.BaseMappingField(default=10)
        self.assertEqual(field.default, 10)

    @mock.patch.object(fields.BaseMappingField, 'clean_value', return_value='cleaned_value')
    def test_init_default_cleaned(self, clean_value: mock.MagicMock) -> None:
        field = fields.BaseMappingField(default='raw_value', field_type=str)
        self.assertEqual(field.default, 'cleaned_value')
        clean_value.assert_called_once_with('raw_value')

    @mock.patch.object(fields.BaseMappingField, 'validate', return_value='validated_value')
    def test_init_default_validated(self, validate: mock.MagicMock) -> None:
        field = fields.BaseMappingField(default='raw_value', field_type=str)
        self.assertEqual(field.default, 'raw_value')
        validate.assert_called_once_with('raw_value')

    def test_init_default_empty_dict(self) -> None:
        with self.assertRaises(ValueError) as context:
            _ = fields.BaseMappingField(default={})
        self.assertEqual(
            str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
        )

    def test_init_default_empty_list(self) -> None:
        with self.assertRaises(ValueError) as context:
            _ = fields.BaseMappingField(default=[])
        self.assertEqual(
            str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
        )

    def test_init_default_empty_set(self) -> None:
        with self.assertRaises(ValueError) as context:
            _ = fields.BaseMappingField(default=set())
        self.assertEqual(
            str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
        )

    def test_init_readonly(self) -> None:
        field = fields.BaseMappingField()
        self.assertFalse(field.readonly)
        field = fields.BaseMappingField(readonly=True)
        self.assertTrue(field.readonly)

    def test_init_required(self) -> None:
        field = fields.BaseMappingField()
        self.assertFalse(field.required)
        field = fields.BaseMappingField(required=True)
        self.assertTrue(field.required)

    def test_init_extra_keys(self) -> None:
        field = fields.BaseMappingField(extra_key1='value1', extra_key2='value2')
        self.assertEqual(field.extra_key1, 'value1')
        self.assertEqual(field.extra_key2, 'value2')


class BaseMappingObjectTestCase:
    ## Class used to test object functionality with different fields
    ## use it as inheritance on every field test case and set the attributes below
    # field: fields.BaseMappingField = fields.BaseMappingField  # noqa:
    # choices: set = {'default', 'updated'}
    # value_invalid_type: int = 123
    # value_init: str = 'default'
    # value_update: str = 'updated'

    def test_init_without_value(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        obj = TestField()
        self.assertEqual(obj.f1, Undefined)
        self.assertEqual(obj['f1'], Undefined)

    def test_choices(self) -> None:
        if self.choices is None:
            return

        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        obj = TestField(f1=self.value_init)
        obj.f1 = self.value_update
        self.assertEqual(obj.f1, self.value_update)
        self.assertEqual(obj['f1'], self.value_update)

    def test_instance(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        TestField(f1=self.value_init)

    def test_instance_invalid_type(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        with self.assertRaises(TypeError) as context:
            TestField(f1=self.value_invalid_type)

        self.assertEqual(
            str(context.exception),
            f'Field f1 must be of type {self.field.field_type.__name__}, got {type(self.value_invalid_type).__name__}.',
        )

    def test_invalid_choice(self) -> None:
        if self.choices is None:
            return

        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1='invalid_choice')
        self.assertEqual(
            str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = 'invalid_choice'
        self.assertEqual(
            str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = 'invalid_choice'
        self.assertEqual(
            str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
        )

    def test_readonly_field(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(readonly=True)

        with self.assertRaises(ReadonlyError) as context:
            obj = TestField(f1=self.value_init)
        self.assertEqual(str(context.exception), 'Field f1 is readonly.')

        obj = TestField()
        with self.assertRaises(ReadonlyError) as context:
            obj.f1 = self.value_update
        self.assertEqual(str(context.exception), 'Field f1 is readonly.')

        with self.assertRaises(ReadonlyError) as context:
            obj['f1'] = self.value_update
        self.assertEqual(str(context.exception), 'Field f1 is readonly.')

    def test_required_field(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(required=True)

        with self.assertRaises(RequiredError) as context:
            _ = TestField()
        self.assertEqual(str(context.exception), 'Field f1 is required.')

    def test_value_init(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        obj = TestField(f1=self.value_init)
        self.assertEqual(obj.f1, self.value_init)
        self.assertEqual(obj['f1'], self.value_init)

    def test_value_update_attribute(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        obj = TestField(f1=self.value_init)
        obj.f1 = self.value_update
        self.assertEqual(obj.f1, self.value_update)
        self.assertEqual(obj['f1'], self.value_update)

    def test_value_update_key(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field()

        obj = TestField(f1=self.value_init)
        obj['f1'] = self.value_update
        self.assertEqual(obj.f1, self.value_update)
        self.assertEqual(obj['f1'], self.value_update)


class BoolFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.BoolField = fields.BoolField
    choices: set = {True, False}
    value_invalid_type: int = 123
    value_init: bool = True
    value_update: bool = False

    def test_attr_type(self) -> None:
        field = fields.BoolField()
        self.assertEqual(field.field_type, bool)

    def test_clean_value_true(self) -> None:
        field = fields.BoolField()
        self.assertTrue(field.clean_value(1))
        self.assertTrue(field.clean_value(True))  # noqa: FBT003
        self.assertTrue(field.clean_value('y'))
        self.assertTrue(field.clean_value('yes'))
        self.assertTrue(field.clean_value('on'))

    def test_clean_value_false(self) -> None:
        field = fields.BoolField()
        self.assertFalse(field.clean_value(0))
        self.assertFalse(field.clean_value(False))  # noqa: FBT003
        self.assertFalse(field.clean_value('n'))
        self.assertFalse(field.clean_value('no'))
        self.assertFalse(field.clean_value('off'))

    def test_invalid_clean_value(self) -> None:
        field = fields.BoolField()
        self.assertEqual(field.clean_value('invalid'), 'invalid')


class ChoiceFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.ChoiceField = fields.ChoiceField
    choices: set = {'a', 'b', 'c'}
    value_invalid_type: int = 123
    value_init: str = 'a'
    value_update: str = 'b'

    def test_attr_type(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.field_type, str)

    def test_min_size_default_value(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.min_size, 0)

    def test_max_size_default_value(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.max_size, maxsize)

    def test_validate_min_size(self) -> None:
        field = fields.StrField(min_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate('a')
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")

    def test_validate_max_size(self) -> None:
        field = fields.StrField(max_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate('abc')
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")

    def test_validate_regex(self) -> None:
        field = fields.StrField(regex=r'^[a-z]+$')
        with self.assertRaises(ValueError) as context:
            field.validate('abc123')
        self.assertEqual(
            str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
        )


class DateFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.DateField = fields.DateField
    choices: set = {Date(2026, 1, 1), Date(2026, 1, 2)}
    value_invalid_type: int = 123
    value_init: Date = Date(2026, 1, 1)
    value_update: Date = Date(2026, 1, 2)

    def test_attr_type(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.field_type, Date)

    def test_clean_value_str_isoformat(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.clean_value('2026-01-01'), Date(2026, 1, 1))

    def test_clean_value_str_everysk(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.clean_value('20260101'), Date(2026, 1, 1))

    def test_clean_value_date(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.clean_value(Date(2026, 1, 1)), Date(2026, 1, 1))

    def test_clean_value_python_date(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.clean_value(date(2026, 1, 1)), Date(2026, 1, 1))

    def test_invalid_isoformat(self) -> None:
        field = fields.DateField()
        with self.assertRaises(ValueError) as context:
            field.clean_value('01-01-2026')
        self.assertEqual(str(context.exception), "Invalid isoformat string: '01-01-2026'")

    def test_invalid_everysk_format(self) -> None:
        field = fields.DateField()
        self.assertEqual(field.clean_value('2026/01/01'), '2026/01/01')

    def test_min_date_validation(self) -> None:
        field = fields.DateField(min_date=Date(2026, 1, 1))
        with self.assertRaises(ValueError) as context:
            field.validate(Date(2025, 12, 31))
        self.assertEqual(
            str(context.exception),
            "The value '2025-12-31' for field 'None' must be greater than or equal to 2026-01-01.",
        )

    def test_max_date_validation(self) -> None:
        field = fields.DateField(max_date=Date(2026, 12, 31))
        with self.assertRaises(ValueError) as context:
            field.validate(Date(2027, 1, 1))
        self.assertEqual(
            str(context.exception), "The value '2027-01-01' for field 'None' must be less than or equal to 2026-12-31."
        )


class DateTimeFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.DateTimeField = fields.DateTimeField
    choices: set = {DateTime(2026, 1, 1), DateTime(2026, 1, 2)}
    value_invalid_type: int = 123
    value_init: DateTime = DateTime(2026, 1, 1)
    value_update: DateTime = DateTime(2026, 1, 2)

    def test_attr_type(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.field_type, DateTime)

    def test_clean_value_str_isoformat(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value('2026-01-01T12:30:45'), DateTime(2026, 1, 1, 12, 30, 45))

    def test_clean_value_str_isoformat_tz(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(
            field.clean_value('2026-01-01T12:30:45+00:00'), DateTime(2026, 1, 1, 12, 30, 45, tzinfo=ZoneInfo('UTC'))
        )

    def test_clean_value_str_everysk(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))

    def test_clean_value_str_everysk_tz(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(
            field.clean_value('2026-01-01 13:00:00+00:00'), DateTime(2026, 1, 1, 13, 0, 0, tzinfo=ZoneInfo('UTC'))
        )

    def test_clean_value_date(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value(Date(2026, 1, 1)), DateTime(2026, 1, 1, 0, 0, 0))

    def test_clean_value_python_date(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value(date(2026, 1, 1)), DateTime(2026, 1, 1, 0, 0, 0))

    def test_clean_value_datetime(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))

    def test_clean_value_python_datetime(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value(datetime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))  # noqa: DTZ001

    def test_force_time(self) -> None:
        # Default is None so time is not changed
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))
        self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))

    def test_force_time_first_minute(self) -> None:
        field = fields.DateTimeField(force_time='FIRST_MINUTE')
        self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 0, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))

    def test_force_time_last_minute(self) -> None:
        field = fields.DateTimeField(force_time='LAST_MINUTE')
        self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 23, 59, 59, 999999))
        self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
        self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
        self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 23, 59, 59, 999999))

    def test_force_time_midday(self) -> None:
        field = fields.DateTimeField(force_time='MIDDAY')
        self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 12, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
        self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 12, 0, 0))

    def test_force_time_now(self) -> None:
        fixed_now = DateTime(2026, 1, 1, 15, 45, 30)

        with mock.patch('everysk.core.mapping.fields.DateTime.now', return_value=fixed_now):
            field = fields.DateTimeField(force_time='NOW')
            self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), fixed_now)
            self.assertEqual(field.clean_value('2026-01-01T13:00:00'), fixed_now)
            self.assertEqual(field.clean_value('2026-01-01 13:00:00'), fixed_now)
            self.assertEqual(field.clean_value('2026-01-01'), fixed_now)

    def test_invalid_isoformat(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value('01-01-2026 12:30:45'), '01-01-2026 12:30:45')

    def test_invalid_everysk_format(self) -> None:
        field = fields.DateTimeField()
        self.assertEqual(field.clean_value('2026/01/01 12:30:45'), '2026/01/01 12:30:45')

    def test_min_date_validation(self) -> None:
        field = fields.DateTimeField(min_date=DateTime(2026, 1, 1, 0, 0, 0))
        with self.assertRaises(ValueError) as context:
            field.validate(DateTime(2025, 12, 31, 23, 59, 59))
        self.assertEqual(
            str(context.exception),
            "The value '2025-12-31 23:59:59+00:00' for field 'None' must be greater than or equal to 2026-01-01 00:00:00+00:00.",
        )

    def test_max_date_validation(self) -> None:
        field = fields.DateTimeField(max_date=DateTime(2026, 12, 31, 23, 59, 59))
        with self.assertRaises(ValueError) as context:
            field.validate(DateTime(2027, 1, 1, 0, 0, 0))
        self.assertEqual(
            str(context.exception),
            "The value '2027-01-01 00:00:00+00:00' for field 'None' must be less than or equal to 2026-12-31 23:59:59+00:00.",
        )


class DictFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.DictField = fields.DictField
    choices: set = None
    value_invalid_type: int = 123
    value_init: dict = {'a': 1}
    value_update: dict = {'b': 2}

    def setUp(self) -> None:
        self._field = fields.DictField(readonly=True)
        self._value = self._field.clean_value({'key': 'value'})

    def test_attr_type(self) -> None:
        field = fields.DictField()
        self.assertEqual(field.field_type, dict)

    def test_clean_value_readonly(self) -> None:
        self.assertEqual(self._value, {'key': 'value'})
        self.assertIsInstance(self._value, fields.DictField.ReadonlyDict)

    def test_clean_value_readonly_clear(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value.clear()

    def test_clean_value_readonly_del(self) -> None:
        with self.assertRaises(ReadonlyError):
            del self._value['key']

    def test_clean_value_readonly_pop(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value.pop('key')

    def test_clean_value_readonly_popitem(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value.popitem()

    def test_clean_value_readonly_set(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value['key'] = 'new_value'

    def test_clean_value_readonly_setdefault(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value.setdefault('key', 'new_value')

    def test_clean_value_readonly_update(self) -> None:
        with self.assertRaises(ReadonlyError):
            self._value.update({'new_key': 'new_value'})


class EmailFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.EmailField = fields.EmailField
    choices: set = {'a@b.c', 'd@e.f'}
    value_invalid_type: int = 123
    value_init: str = 'a@b.c'
    value_update: str = 'd@e.f'

    def test_attr_type(self) -> None:
        field = fields.EmailField()
        self.assertEqual(field.field_type, str)

    def test_validate_valid_email(self) -> None:
        field = fields.EmailField()
        field.validate('fscariott@everysk.com')

    def test_validate_invalid_email(self) -> None:
        field = fields.EmailField()
        with self.assertRaises(ValueError) as context:
            field.validate('invalid-email')
        self.assertEqual(str(context.exception), 'Key None must be an e-mail.')

    def test_validate_invalid_email_max_size(self) -> None:
        field = fields.EmailField()
        long_email = 'a' * 315 + '@everysk.com'  # total length = 321
        with self.assertRaises(ValueError) as context:
            field.validate(long_email)
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 320.")

    def test_validate_invalid_email_min_size(self) -> None:
        field = fields.EmailField()
        short_email = 'a@b'  # total length = 3
        with self.assertRaises(ValueError) as context:
            field.validate(short_email)
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 5.")

    def test_validate_invalid_email_more_at(self) -> None:
        field = fields.EmailField()
        with self.assertRaises(ValueError) as context:
            field.validate('fscariott@everysk.com@teste')
        self.assertEqual(str(context.exception), 'Key None must be an e-mail.')


class FloatFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.FloatField = fields.FloatField
    choices: set = {1.23, 4.56}
    value_invalid_type: str = 'invalid'
    value_init: float = 1.23
    value_update: float = 4.56

    def test_attr_type(self) -> None:
        field = fields.FloatField()
        self.assertEqual(field.field_type, float)

    def test_clean_value_int(self) -> None:
        field = fields.FloatField()
        self.assertEqual(field.clean_value(123), 123.0)

    def test_clean_value_str(self) -> None:
        field = fields.FloatField()
        self.assertEqual(field.clean_value('123.45'), 123.45)

    def test_clean_value_invalid_str(self) -> None:
        field = fields.FloatField()
        self.assertEqual(field.clean_value('invalid'), 'invalid')

    def test_validate_max_value(self) -> None:
        field = fields.FloatField(max_value=100.0)
        with self.assertRaises(ValueError) as context:
            field.validate(150.0)
        self.assertEqual(
            str(context.exception), "The value '150.0' for field 'None' must be less than or equal to 100.0."
        )

    def test_validate_min_value(self) -> None:
        field = fields.FloatField(min_value=10.0)
        with self.assertRaises(ValueError) as context:
            field.validate(5.0)
        self.assertEqual(
            str(context.exception), "The value '5.0' for field 'None' must be greater than or equal to 10.0."
        )


class IntFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.IntField = fields.IntField
    choices: set = {1, 2}
    value_invalid_type: str = 'invalid'
    value_init: int = 1
    value_update: int = 2

    def test_attr_type(self) -> None:
        field = fields.IntField()
        self.assertEqual(field.field_type, int)

    def test_clean_value_str(self) -> None:
        field = fields.IntField()
        self.assertEqual(field.clean_value('123'), 123)

    def test_clean_value_invalid_str(self) -> None:
        field = fields.IntField()
        self.assertEqual(field.clean_value('invalid'), 'invalid')

    def test_validate_max_value(self) -> None:
        field = fields.IntField(max_value=100)
        with self.assertRaises(ValueError) as context:
            field.validate(150)
        self.assertEqual(str(context.exception), "The value '150' for field 'None' must be less than or equal to 100.")

    def test_validate_min_value(self) -> None:
        field = fields.IntField(min_value=10)
        with self.assertRaises(ValueError) as context:
            field.validate(5)
        self.assertEqual(str(context.exception), "The value '5' for field 'None' must be greater than or equal to 10.")


class IteratorFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.IteratorField = fields.IteratorField
    choices: set = None
    value_invalid_type: str = 'invalid'
    value_init: Iterator = iter([1])
    value_update: Iterator = iter([2])

    def test_attr_type(self) -> None:
        field = fields.IteratorField()
        self.assertEqual(field.field_type, Iterator)

    def test_clean_value_list(self) -> None:
        field = fields.IteratorField()
        value = field.clean_value([1, 2, 3])
        self.assertIsInstance(value, Iterator)
        self.assertListEqual(list(value), [1, 2, 3])

    def test_clean_value_set(self) -> None:
        field = fields.IteratorField()
        value = field.clean_value({3, 1, 2})
        self.assertIsInstance(value, Iterator)
        self.assertListEqual(sorted(value), [1, 2, 3])

    def test_clean_value_str(self) -> None:
        field = fields.IteratorField()
        value = field.clean_value('a,b,c')
        self.assertIsInstance(value, Iterator)
        self.assertListEqual(list(value), ['a', 'b', 'c'])

    def test_clean_value_str_separator(self) -> None:
        field = fields.IteratorField(separator=';')
        value = field.clean_value('a;b;c')
        self.assertIsInstance(value, Iterator)
        self.assertListEqual(list(value), ['a', 'b', 'c'])

    def test_clean_value_tuple(self) -> None:
        field = fields.IteratorField()
        value = field.clean_value((1, 2, 3))
        self.assertIsInstance(value, Iterator)
        self.assertListEqual(list(value), [1, 2, 3])


class ListFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.ListField = fields.ListField
    choices: set = {1, 2}
    value_invalid_type: str = 'invalid'
    value_init: list = [1]
    value_update: list = [2]

    def test_attr_type(self) -> None:
        field = fields.ListField()
        self.assertEqual(field.field_type, list)

    def test_clean_value_readonly(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        self.assertIsInstance(value, fields.ListField.ReadonlyList)
        self.assertListEqual(value, [1, 2, 3])

    def test_clean_value_readonly_append(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.append(4)

    def test_clean_value_readonly_clear(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.clear()

    def test_clean_value_readonly_extend(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.extend([4, 5])

    def test_clean_value_readonly_insert(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.insert(1, 4)

    def test_clean_value_readonly_pop(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.pop()

    def test_clean_value_readonly_remove(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.remove(2)

    def test_clean_value_readonly_reverse(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.reverse()

    def test_clean_value_readonly_sort(self) -> None:
        field = fields.ListField(readonly=True)
        value = field.clean_value([1, 2, 3])
        with self.assertRaises(fields.ReadonlyError):
            value.sort()

    def test_clean_value_set(self) -> None:
        field = fields.ListField()
        value = field.clean_value({3, 1, 2})
        self.assertIsInstance(value, list)
        self.assertListEqual(sorted(value), [1, 2, 3])

    def test_clean_value_str(self) -> None:
        field = fields.ListField()
        value = field.clean_value('a,b,c')
        self.assertIsInstance(value, list)
        self.assertListEqual(value, ['a', 'b', 'c'])

    def test_clean_value_str_separator(self) -> None:
        field = fields.ListField(separator=';')
        value = field.clean_value('a;b;c')
        self.assertIsInstance(value, list)
        self.assertListEqual(value, ['a', 'b', 'c'])

    def test_clean_value_tuple(self) -> None:
        field = fields.ListField()
        value = field.clean_value((1, 2, 3))
        self.assertIsInstance(value, list)
        self.assertListEqual(value, [1, 2, 3])

    def test_invalid_choice_list(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1=[3])
        self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = [3]
        self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")

        with self.assertRaises(ValueError) as context:
            obj['f1'] = [3]
        self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")

    def test_invalid_choice_list_values(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1=[1, 3])
        self.assertEqual(
            str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = [1, 3]
        self.assertEqual(
            str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = [1, 3]
        self.assertEqual(
            str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
        )

    def test_max_size_default_value(self) -> None:
        field = fields.ListField()
        self.assertEqual(field.max_size, maxsize // 8)

    def test_min_size_default_value(self) -> None:
        field = fields.ListField()
        self.assertEqual(field.min_size, 0)

    def test_validate_max_size(self) -> None:
        field = fields.ListField(max_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate([1, 2, 3])
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")

    def test_validate_min_size(self) -> None:
        field = fields.ListField(min_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate([1])
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")


class SetFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.SetField = fields.SetField
    choices: set = {1, 2}
    value_invalid_type: str = 'invalid'
    value_init: set = {1}
    value_update: set = {2}

    def test_attr_type(self) -> None:
        field = fields.SetField()
        self.assertEqual(field.field_type, set)

    def test_clean_value_list(self) -> None:
        field = fields.SetField()
        value = field.clean_value([1, 2, 2, 3])
        self.assertIsInstance(value, set)
        self.assertSetEqual(value, {1, 2, 3})

    def test_clean_value_readonly(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        self.assertIsInstance(value, fields.SetField.ReadonlySet)
        self.assertSetEqual(value, {1, 2, 3})

    def test_clean_value_readonly_add(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.add(4)

    def test_clean_value_readonly_clear(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.clear()

    def test_clean_value_readonly_discard(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.discard(2)

    def test_clean_value_readonly_pop(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.pop()

    def test_clean_value_readonly_remove(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.remove(2)

    def test_clean_value_readonly_update(self) -> None:
        field = fields.SetField(readonly=True)
        value = field.clean_value({1, 2, 3})
        with self.assertRaises(fields.ReadonlyError):
            value.update({4})

    def test_clean_value_str(self) -> None:
        field = fields.SetField()
        value = field.clean_value('a,b,a,c')
        self.assertIsInstance(value, set)
        self.assertSetEqual(value, {'a', 'b', 'c'})

    def test_clean_value_str_separator(self) -> None:
        field = fields.SetField(separator=';')
        value = field.clean_value('a;b;a;c')
        self.assertIsInstance(value, set)
        self.assertSetEqual(value, {'a', 'b', 'c'})

    def test_clean_value_tuple(self) -> None:
        field = fields.SetField()
        value = field.clean_value((1, 2, 2, 3))
        self.assertIsInstance(value, set)
        self.assertSetEqual(value, {1, 2, 3})

    def test_invalid_choice_set(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1={3})
        self.assertEqual(
            str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = {3}
        self.assertEqual(
            str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = {3}
        self.assertEqual(
            str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
        )

    def test_invalid_choice_set_values(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1={1, 3})
        self.assertEqual(
            str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = {1, 3}
        self.assertEqual(
            str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = {1, 3}
        self.assertEqual(
            str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
        )

    def test_max_size_default_value(self) -> None:
        field = fields.SetField()
        self.assertEqual(field.max_size, maxsize // 8)

    def test_min_size_default_value(self) -> None:
        field = fields.SetField()
        self.assertEqual(field.min_size, 0)

    def test_validate_min_size(self) -> None:
        field = fields.SetField(min_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate({1})
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")

    def test_validate_max_size(self) -> None:
        field = fields.SetField(max_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate({1, 2, 3})
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")


class StrFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.StrField = fields.StrField
    choices: set = {'a', 'b', 'c'}
    value_invalid_type: int = 123
    value_init: str = 'a'
    value_update: str = 'b'

    def test_attr_type(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.field_type, str)

    def test_min_size_default_value(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.min_size, 0)

    def test_max_size_default_value(self) -> None:
        field = fields.StrField()
        self.assertEqual(field.max_size, maxsize)

    def test_validate_min_size(self) -> None:
        field = fields.StrField(min_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate('a')
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")

    def test_validate_max_size(self) -> None:
        field = fields.StrField(max_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate('abc')
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")

    def test_validate_regex(self) -> None:
        field = fields.StrField(regex=r'^[a-z]+$')
        with self.assertRaises(ValueError) as context:
            field.validate('abc123')
        self.assertEqual(
            str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
        )


class RegexFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.RegexField = fields.RegexField
    choices: set = None
    value_invalid_type: int = 123
    value_init: re.Pattern = re.compile('a')
    value_update: re.Pattern = re.compile('b')

    def test_attr_type(self) -> None:
        field = fields.RegexField()
        self.assertEqual(field.field_type, re.Pattern)

    def test_clean_value(self) -> None:
        field = fields.RegexField()
        value = field.clean_value(r'^[a-z]+$')
        self.assertIsInstance(value, re.Pattern)
        self.assertEqual(value.pattern, r'^[a-z]+$')


class TupleFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.TupleField = fields.TupleField
    choices: set = (1, 2)
    value_invalid_type: int = 123
    value_init: tuple = (1,)
    value_update: tuple = (2,)

    def test_attr_type(self) -> None:
        field = fields.TupleField()
        self.assertEqual(field.field_type, tuple)

    def test_clean_value_list(self) -> None:
        field = fields.TupleField()
        value = field.clean_value([1, 2, 3])
        self.assertIsInstance(value, tuple)
        self.assertTupleEqual(value, (1, 2, 3))

    def test_clean_value_set(self) -> None:
        field = fields.TupleField()
        value = field.clean_value({3, 1, 2})
        self.assertIsInstance(value, tuple)
        self.assertTupleEqual(tuple(sorted(value)), (1, 2, 3))

    def test_clean_value_str(self) -> None:
        field = fields.TupleField()
        value = field.clean_value('a,b,c')
        self.assertIsInstance(value, tuple)
        self.assertTupleEqual(value, ('a', 'b', 'c'))

    def test_clean_value_str_separator(self) -> None:
        field = fields.TupleField(separator=';')
        value = field.clean_value('a;b;c')
        self.assertIsInstance(value, tuple)
        self.assertTupleEqual(value, ('a', 'b', 'c'))

    def test_invalid_choice_tuple(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1=(3,))
        self.assertEqual(
            str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = (3,)
        self.assertEqual(
            str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = (3,)
        self.assertEqual(
            str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
        )

    def test_invalid_choice_tuple_values(self) -> None:
        class TestField(BaseMapping):
            __validate_fields__: bool = True
            f1 = self.field(choices=self.choices)

        with self.assertRaises(ValueError) as context:
            _ = TestField(f1=(1, 3))
        self.assertEqual(
            str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
        )
        obj = TestField()
        with self.assertRaises(ValueError) as context:
            obj.f1 = (1, 3)
        self.assertEqual(
            str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
        )

        with self.assertRaises(ValueError) as context:
            obj['f1'] = (1, 3)
        self.assertEqual(
            str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
        )

    def test_min_size_default_value(self) -> None:
        field = fields.TupleField()
        self.assertEqual(field.min_size, 0)

    def test_max_size_default_value(self) -> None:
        field = fields.TupleField()
        self.assertEqual(field.max_size, maxsize // 8)

    def test_validate_min_size(self) -> None:
        field = fields.TupleField(min_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate((1,))
        self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")

    def test_validate_max_size(self) -> None:
        field = fields.TupleField(max_size=2)
        with self.assertRaises(ValueError) as context:
            field.validate((1, 2, 3))
        self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")


class URLFieldTestCase(TestCase, BaseMappingObjectTestCase):
    field: fields.URLField = fields.URLField
    choices: set = {'http://example.com', 'https://example.com'}
    value_invalid_type: int = 123
    value_init: str = 'http://example.com'
    value_update: str = 'https://example.com'

    def test_attr_type(self) -> None:
        field = fields.URLField()
        self.assertEqual(field.field_type, str)

    def test_validate_http_url(self) -> None:
        field = fields.URLField()
        field.validate('http://exampe.com/path?var=1&var=2')

    def test_validate_https_url(self) -> None:
        field = fields.URLField()
        field.validate('https://exampe.com/path?var=1&var=2')

    def test_validate_localhost_url(self) -> None:
        field = fields.URLField()
        field.validate('http://localhost:8080/path?var=1&var=2')

    def test_validate_invalid_scheme(self) -> None:
        field = fields.URLField()
        with self.assertRaises(ValueError) as context:
            field.validate('tp://example.com/path?var=1&var=2')
        self.assertEqual(str(context.exception), 'Key None must be an URL.')

    def test_validate_invalid_domain(self) -> None:
        field = fields.URLField()
        with self.assertRaises(ValueError) as context:
            field.validate('http://example com/path?var=1&var=2')
        self.assertEqual(str(context.exception), 'Key None must be an URL.')

    def test_validate_invalid_query(self) -> None:
        field = fields.URLField()
        with self.assertRaises(ValueError) as context:
            field.validate('http://localhost:8080/path?var=1 var=2')

        self.assertEqual(str(context.exception), 'Key None must be an URL.')
