###############################################################################
#
# (C) Copyright 2023 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.
#
###############################################################################
import re

from collections.abc import Iterator
from sys import maxsize as max_int
from typing import Any, Callable
from urllib.parse import urlparse

from everysk.utils import bool_convert

from everysk.core.datetime import DateTime, Date
from everysk.core.exceptions import ReadonlyError, FieldValueError
from everysk.core.object import BaseField


## Functions
def _do_nothing(*args, **kwargs):
    # pylint: disable=unused-argument
    raise ReadonlyError('This field value cannot be changed.')


def _min_max_validate(min_value: Any, max_value: Any, value: Any, attr_name: str) -> None:
    """
    Helper function to validate if value is between min and max for fields.

    Args:
        min_value (Any): The min value to be checked.
        max_value (Any): The max value to be checked.
        value (Any): The value used to validate.
        attr_name (str): The name of the attribute to be used on errors.

    Usage:
        >>> _min_max_validate(0, 10, 5, "my_field")
        # No exception is raised

        >>> _min_max_validate(0, 10, 15, "my_field")
        # FieldValueError is raised with the message:
        # "The value '15' for field 'my_field' must be between 0 and 10."

    Raises:
        FieldValueError: If the value is not between min and max.
    """
    if value is not None and value is not Undefined:
        min_check = False
        max_check = False

        # Check if min_value and max_value are callable
        min_value = min_value if not callable(min_value) else min_value()
        max_value = max_value if not callable(max_value) else max_value()

        if min_value is not None and min_value is not Undefined:
            min_check = value < min_value

        if max_value is not None and max_value is not Undefined:
            max_check = value > max_value

        if min_check or max_check:
            raise FieldValueError(
                f"The value '{value}' for field '{attr_name}' must be between {min_value} and {max_value}."
            )


## Classes
# For the VSCode autocomplete works correctly, we need to say what is the
# type that the method __new__ returns
class Field(BaseField):
    attr_type: Any = None

    def __new__(cls, *args, **kwargs) -> 'Field':
        return super().__new__(cls)

    def __init__(
        self,
        default: Any = None,
        required: bool = False,
        readonly: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(attr_type=self.attr_type, default=default, required=required, readonly=readonly, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)


class BoolField(Field):
    attr_type: bool = bool

    def __new__(cls, *args, **kwargs) -> bool:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Converts the  given value to a boolean if possible using the 'convert_boolean' function.
        (Check the function's documentation for more information)

        Args:
            value (Boolean): The value to be converted. Can be of any type

        Usage:
            >>> from everysk.core.fields import BoolField
            >>> bool_field = BoolField()
            >>> bool_field.clean_value("y")
            >>> True

            >>> bool_field.clean_value("n")
            >>> False

            >>> bool_field.clean_value("a")
            >>> ValueError: Invalid truth value 'a'

        Returns:
            Any: The value converted to its boolean corresponding
        """
        # https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool
        # The module distutils is deprecated, then we put the function code here
        if value is not None and value is not Undefined:
            value = bool_convert(value)

        return super().clean_value(value)


class ChoiceField(Field):
    attr_type: str = str
    choices: list[str] = None

    def __new__(cls, *args, **kwargs) -> str:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: str,
        choices: list,
        readonly: bool = False,
        required: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default=default, required=required, readonly=readonly, choices=choices, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

        # Choices always need to be a list
        if not self.choices:
            self.choices = []

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is in list of choices.
        We discard the value Undefined.

        Args:
            attr_name (str): Name of the attribute being validated
            value (Any): The value up for validation against the choices available.
            attr_type (type = None): Type of the field we are trying to validate

        Usage:
            >>> from everysk.core.fields import ChoiceField
            >>> from everysk.core.exceptions import FieldValueError
            >>> field = ChoiceField(default="option1", choices=["option1", "option2", "option3"])
            >>> try:
            >>> ... field.validate("test_field", "option1")
            >>> ... print("Validation successful")
            >>> except FieldValueError as e:
            >>> ... print(f"Validation error: {e}")
            >>> Validation successful

            >>> try:
            >>> ... field.validate("test_field", "option4")
            >>> ... print("Validation successful")
            >>> except Exception as e:
            >>> ... print(f"Validation error: {e}")
            >>> Validation error: The value 'option4' for field 'test_field' must be in this list ['option1', 'option2', 'option3'].

        Raises:
            FieldValueError: If value type doesn't match with the required type.

        Returns:
            None: This method does not return any value. It either completes successfully or raises an exception
        """
        if value is not Undefined and value not in self.choices:
            raise FieldValueError(
                f"The value '{value}' for field '{attr_name}' must be in this list {self.choices}."
            )
        super().validate(attr_name, value, attr_type)


class DateField(Field):
    attr_type: Date = Date
    min_date: Date | Callable = None
    max_date: Date | Callable = None

    def __new__(cls, *args, **kwargs) -> Date:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        min_date: Date = None,
        max_date: Date = None,
        required: bool = False,
        readonly: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default, min_date=min_date, max_date=max_date, required=required, readonly=readonly, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        This method simple converts Date strings to Date object in the Everysk format

        Args:
            value (Any): Desired date string to be converted

        Usage:
            >>> from everysk.core.fields import DateField
            >>> date_field = DateField()
            >>> date_field.clean_value("20140314")
            Date(2014, 3, 14)
            >>> date_field.clean_value("2014-03-14")
            Date(2014, 3, 14)

        Returns:
            A new Date object representing the converted date

        Raises:
            ValueError: If the string is not represented in either ISO format ("YYYY-MM-DD") or Everysk format ("YYYYMMDD").
        """
        if isinstance(value, str):
            if '-' in value:
                value = Date.fromisoformat(value)
            else:
                # Everysk format
                value = Date.strptime(value, '%Y%m%d')

        return super().clean_value(value)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is greater than min and lower than max including both values.

        Args:
            attr_name (str): Name of the attribute used for error checking
            value (Any): Value used for validation
            attr_type (type = None): Type of the field we are trying to validate

        Usage:
            >>> from everysk.core.fields import DateField
            >>> from everysk.core.datetime import Date
            >>> date_field = DateField(min_date=Date(2023, 1, 1), max_date=Date(2023, 12, 31))

            >>> try:
            >>> ... date_field.validate("example_date", Date(2023, 6, 15))
            >>> ... print("June 15, 2023, is a valid date")
            >>> except Exception as e:
            >>> ... print(f"Validation error: {e}")
            >>> June 15, 2023, is a valid date

            >>> try:
            >>> ... date_field.validate("example_date", Date(2022, 12, 31))
            >>> ... print("December 31, 2022, is a valid date")
            >>> except Exception as e:
            >>> ... print(f"validation error: {e}")
            >>> Validation error: The value '2022-12-31' for field 'example_date' must be between 2023-01-01 and 2023-12-31.

        Returns:
            Either the value is between min_date and max_date

        Raises:
            FieldValueError: If the value is not between min_date and max_date
        """
        _min_max_validate(self.min_date, self.max_date, value, attr_name)
        return super().validate(attr_name, value, attr_type)


class DateTimeField(Field):
    attr_type:DateTime = DateTime
    min_date: DateTime | Callable = None
    max_date: DateTime | Callable = None
    force_time: str = None

    def __new__(cls, *args, **kwargs) -> DateTime:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        min_date: DateTime = None,
        max_date: DateTime = None,
        force_time: str = 'FIRST_MINUTE',
        required: bool = False,
        readonly: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default, min_date=min_date, max_date=max_date, force_time=force_time, required=required, readonly=readonly, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Converts a DateTime string to a DateTime object using the 'fromisoformat' function.
        (check 'fromisoformat' function for more information)

        Args:
            value (Any): desired DateTime string to be converted

        Usage:
            >>> from everysk.core.fields import DateTimeField
            >>> date_time_field = DateTimeField()

            >>> date_time_field.clean_value("2023-03-15")
            >>> DateTime(2023, 3, 15, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))

            >>> date_time_field.clean_value("2023-03-15T14:30:00")
            >>> DateTime(2023, 3, 15, 14, 30, tzinfo=zoneinfo.ZoneInfo(key='UTC'))

            >>> date_time_field.clean_value("14-03-2023")
            >>> ValueError: time data "14-03-2023" does not match format: "%Y-%m-%d"

        Returns:
            Any: a new DateTime object formatted

        Raises:
            ValueError: if the input is not formatted in the ISO format
        """
        # Convert DateTime strings to DateTime object
        if isinstance(value, str):
            if not ':' in value:
                value: DateTime = DateTime.fromisoformat(value).force_time(self.force_time)
            else:
                value: DateTime = DateTime.fromisoformat(value)
        elif Date.is_date(value):
            value: DateTime = DateTime.fromisoformat(value.isoformat())

        return super().clean_value(value)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks is the value passed is in the correct range between min and max, as well as required, required_lazy, and attribute_type validations.

        Args:
            attr_name (str):
                The name of the attribute being validated. This is used for identification in error messages.

            value (Any):
                The value to be validated for the specified attribute.

            attr_type (type, optional):
                The expected type of the attribute. If provided, the value is checked to ensure it is
                of this type.

        Usage:
            >>> from everysk.core.datetime import Date
            >>> from everysk.core.fields import DateTimeField
            >>> from everysk.core.datetime import DateTime
            >>> date_time_field = DateTimeField(min_date=DateTime(2023, 1, 1), max_date=DateTime(2023, 12, 31))

            >>> try:
            >>> ... date_time_field.validate("test_field", DateTime(2023, 6, 15))
            >>> ... print("Validation successful")
            >>> except FieldValueError as e:
            >>> ... print(f"Validation error: {e}")

        Raises:
            FieldValueError: if the value provided is not between min and max values
        """
        _min_max_validate(self.min_date, self.max_date, value, attr_name)
        return super().validate(attr_name, value, attr_type)


class DictField(Field):
    attr_type: dict = dict

    class ReadonlyDict(dict):
        __setitem__ = _do_nothing
        __delitem__ = _do_nothing
        pop = _do_nothing
        popitem = _do_nothing
        clear = _do_nothing
        update = _do_nothing
        setdefault = _do_nothing

    def __new__(cls, *args, **kwargs) -> dict:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, default: Any = None, required: bool = False, readonly: bool = False, required_lazy: bool = False, empty_is_none: bool = False, **kwargs) -> None:
        # When the field is readonly, we need to change the content to.
        if readonly and default:
            default = self.ReadonlyDict(default)

        super().__init__(default, required, readonly, required_lazy, empty_is_none, **kwargs)


class EmailField(Field):
    attr_type: str = str

    def __new__(cls, *args, **kwargs) -> str:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        # The maximum length of an email is 320 characters per RFC 3696 section 3,
        # and at least 3 digits a@b
        if value is not None and value is not Undefined and isinstance(value, str) and ('@' not in value or len(value) > 320 or len(value) < 3):
            raise FieldValueError(f'Key {attr_name} must be an e-mail.')

        try:
            # Get the cases where we could have more than one @ in the string
            user, domain = value.split('@') # pylint: disable=unused-variable
        except ValueError as error:
            raise FieldValueError(f'Key {attr_name} must be an e-mail.') from error

        return super().validate(attr_name, value, attr_type)


class FloatField(Field):
    attr_type: float = float
    min_size: float = None
    max_size: float = None

    def __new__(cls, *args, **kwargs) -> float:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(self,
        default: Any = None,
        min_size: float = float('-inf'),
        max_size: float = float('inf'),
        required: bool = False,
        readonly: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default, required, readonly, min_size=min_size, max_size=max_size, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Convert a float string to a float object

        Args:
            value (Any): Value desired for conversion to float

        Usage:
            >>> from everysk.core.fields import FloatField
            >>> float_field = FloatField()

            >>> float_field.clean_value("3.14")
            >>> 3.14

            >>> float_field.clean_value(123)
            >>> 123.0

            >>> float_field.clean_value('abc')
            >>> ValueError: could not convert string to float: 'abc'

        Returns:
            Float: if value is able to be converted to float object

        Raises:
            ValueError: If value could not be converted to a float
        """
        # Convert Float strings to float object
        if isinstance(value, (int, str)):
            value = float(value)

        return super().clean_value(value)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is greater than min and lower than max including both values.
        It also validates the `required`, `required_lazy`, and `attr_type` parameters according to the `_validate()` function.

        Raises:
            FieldValueError: If value is not between min and max.

        Usage:
            >>> from everysk.core.fields import FloatField
            >>> from everysk.core.exceptions import FieldValueError
            >>> float_field = FloatField(min_size=0.0, max_size=100.0)

            >>> try:
            >>> ... float_field.validate("test_field", 50.0)
            >>> ... print("Validation successful: 50.0 is within the range.")
            >>>  except FieldValueError as e:
            >>> ... print(f"Validation error: {e}")
            >>> Validation successful: 50.0 is within the range.

            >>> try:
            >>> ... float_field.validate("test_field", -10.0)
            >>> ... print("Validation successful: -10.0 is within the range.")
            >>>  except FieldValueError as e:
            >>> ... print(f"Validation error: {e}")
            >>> Validation error: The value '-10.0' for field 'test_field' is not within the allowed range.
        """
        _min_max_validate(min_value=self.min_size, max_value=self.max_size, value=value, attr_name=attr_name)
        return super().validate(attr_name, value, attr_type)


class IntField(Field):
    attr_type: int = int
    min_size: int = None
    max_size: int = None

    def __new__(cls, *args, **kwargs) -> int:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(self,
        default: Any = None,
        min_size: int = -max_int,
        max_size: int = max_int,
        required: bool = False,
        readonly: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default, required, readonly, min_size=min_size, max_size=max_size, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Converts a value to an integer if it can be converted

        Args:
            value (Any): Value to convert


        Usage:
            >>> from everysk.core.fields import IntField
            >>> int_field = IntField()

            >>> int_field.clean_value("123")
            >>> 123

            >>> int_field.clean_value("abc")
            >>> ValueError: invalid literal for int() with base 10: 'abc'

        Returns:
            Int: value converted to an integer

        Raises:
            ValueError: if the string cannot be converted to an integer
        """
        # Convert Int strings to int object
        if isinstance(value, str):
            value = int(value)

        return super().clean_value(value)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is greater than min and lower than max including both values.
        It also validates the `required`, `required_lazy`, and `attr_type` parameters as per the `_validate()` function.

        Args:
            attr_name (str):
                The name of the attribute being validated. This name is used in the error message if the
                validation fails.

            value (Any):
                The value to be validated. The function checks if this value is within the defined min and max range.

            attr_type (type, optional):
                The type against which the value will be validated. If provided, the method
                ensures that the value is of this type.

        Usage:
            >>> from everysk.core.fields import IntField
            >>> from everysk.core.exceptions import FieldValueError
            >>> int_field = IntField(min_size=1, max_size=10)

            >>> try:
            >>> ... int_field.validate("test_field", 5)
            >>> ... print("Validation successful")
            >>> except FieldValueError as e:
            >>> ... print(f"Validation error: {e}")
            >>> Validation successful

            >>> try:
            >>> ... int_field.validate("test_field", -1)
            >>> ... print("validation successful")
            >>> except FieldValueError as e:
            >>> ...print(f"Validation error: {e}")
            >>> Validation error: The value '-1' for field 'test_field' must be between 1 and 10.

        Raises:
            FieldValueError: If value is not between min_size and max.
        """
        _min_max_validate(min_value=self.min_size, max_value=self.max_size, value=value, attr_name=attr_name)
        return super().validate(attr_name, value, attr_type)


class IteratorField(Field):
    attr_type: Iterator = Iterator

    def __new__(cls, *args, **kwargs) -> Iterator:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Cleans the given value before storing it.

        Args:
            value (Any): The value to be cleaned.

        Returns:
            Any: The cleaned value.

        """
        # Convert List/Str to iterators
        if isinstance(value, (list, str)):
            value = iter(value)

        return super().clean_value(value)


# https://stackoverflow.com/questions/855191/how-big-can-a-python-list-get#comment112727918_15739630
# so on 64 systems is maxsize / 8
class ListField(Field):
    attr_type: list = list
    min_size: int = None
    max_size: int = None

    class ReadonlyList(list):
        __setitem__ = _do_nothing
        __delitem__ = _do_nothing
        append = _do_nothing
        clear = _do_nothing
        extend = _do_nothing
        insert = _do_nothing
        pop = _do_nothing
        remove = _do_nothing
        reverse = _do_nothing
        sort = _do_nothing

    def __new__(cls, *args, **kwargs) -> list:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        min_size: int = 0,
        max_size: int = max_int / 8,
        readonly: bool = False,
        required: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        if min_size < 0:
            raise FieldValueError('List min_size cloud not be a negative number.')

        # When the field is readonly, we need to change the content to.
        if readonly and (default is not None or default is not Undefined):
            default = self.ReadonlyList(default)

        super().__init__(default=default, min_size=min_size, max_size=max_size, readonly=readonly, required=required, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def clean_value(self, value: Any) -> Any:
        """
        Clean the value before storing it.
        This method is used to ensure that the value is in the correct format before storing it.
        This method checks if the value is a string and converts it into a list if necessary.
        It then calls the parent class's clean_value method to perform additional cleaning.

        Args:
            value (Any): The value to be cleaned.

        Example Usage:
            >>> field = Field()
            >>> field.clean_value("example")
            ['example']

        Returns:
            Any: The cleaned value.
        """
        if isinstance(value, str):
            value = [value]

        return super().clean_value(value)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is greater than min_size and lower than max_size including both values.

        Raises:
            FieldValueError: If value is not list instance or if is not between min_size and max_size
        """
        if value is not None and value is not Undefined:
            if not isinstance(value, list):
                raise FieldValueError(f"The '{attr_name}' value must be a list.")
            if not self.min_size <= len(value) <= self.max_size:
                raise FieldValueError(
                    f"The attribute '{attr_name}' is not within the specified list range. min_size: {self.min_size} max_size: {self.max_size}"
                )
        super().validate(attr_name, value, attr_type)


class SetField(Field):
    attr_type: set = set
    min_size: int = None
    max_size: int = None

    class ReadonlySet(set):
        __setitem__ = _do_nothing
        __delitem__ = _do_nothing
        add = _do_nothing
        clear = _do_nothing
        discard = _do_nothing
        pop = _do_nothing
        remove = _do_nothing
        update = _do_nothing

    def __new__(cls, *args, **kwargs) -> set:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        min_size: int = 0,
        max_size: int = max_int / 8,
        readonly: bool = False,
        required: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        if min_size < 0:
            raise FieldValueError('Set min_size cloud not be a negative number.')

        # When the field is readonly, we need to change the content to.
        if readonly and (default is not None or default is not Undefined):
            default = self.ReadonlySet(default)

        super().__init__(default=default, min_size=min_size, max_size=max_size, readonly=readonly, required=required, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Checks if value is greater than min_size and lower than max_size including both values.

        Raises:
            FieldValueError: If value is not list instance.
            FieldValueError: If value is not between min_size and max_size.
        """
        if value is not None and value is not Undefined and isinstance(value, set):
            if not self.min_size <= len(value) <= self.max_size:
                raise FieldValueError(
                    f"The attribute '{attr_name}' is not within the specified set range. min_size: {self.min_size} max_size: {self.max_size}"
                )
        super().validate(attr_name, value, attr_type)


class StrField(Field):
    """
    Represents a string field in an entity.
    This field type is used to store string values and provides validation
    for string attributes based on specified constraints such as minimum and maximum size
    and regular expression pattern matching.

    Args:
        default (Any, optional):
            The default value for the field. Defaults to None.
        min_size (int, optional):
            The minimum allowed length for the string. Defaults to 0.
        max_size (int, optional):
            The maximum allowed length for the string. Defaults to the maximum integer value.
        regex (str, optional):
            The regular expression pattern to match against the string value. Defaults to None.
        readonly (bool, optional):
            Whether the field is read-only. Defaults to False.
        required (bool, optional):
            Whether the field is required. Defaults to False.
        required_lazy (bool, optional):
            Whether the field is lazily required. Defaults to False.
        empty_is_none (bool, optional):
            Whether an empty string should be treated as None. Defaults to False.

    Raises:
        FieldValueError: If the minimum size is negative
    """

    attr_type: str = str
    min_size: int = None
    max_size: int = None
    regex: re.Pattern = None

    def __new__(cls, *args, **kwargs) -> str:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        min_size: int = 0,
        max_size: int = max_int,
        regex: str = None,
        readonly: bool = False,
        required: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        if min_size < 0:
            raise FieldValueError('String min_size cloud not be a negative number.')
        super().__init__(default=default, regex=regex, min_size=min_size, max_size=max_size, readonly=readonly, required=required, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        """
        Validates a string attribute against specified constraints.

        Args:
            attr_name (str):
                The name of the attribute being validated.
            value (Any):
                The value of the attribute to be validated.
            attr_type (type, optional):
                The expected type of the attribute. Defaults to None.

        Raises:
            FieldValueError:
                If the provided value does not meet the specified constraints.
                If the value does not match the regular expression pattern.
                If the length of the value is outside the specified range.
        """
        # First let all default checks be done.
        super().validate(attr_name, value, attr_type)

        if self.regex and value and value is not Undefined and not self.regex.match(value):
            raise FieldValueError(
                f"The value '{value}' for field '{attr_name}' must match with this regex: {self.regex.pattern}."
            )

        # Then we check for the size.
        try:
            if value is not None and value is not Undefined:
                _min_max_validate(min_value=self.min_size, max_value=self.max_size, value=len(value), attr_name=attr_name)
        except FieldValueError as error:
            raise FieldValueError(
                f"The length '{len(value)}' for attribute '{attr_name}' must be between '{self.min_size}' and '{self.max_size}'."
            ) from error


class RegexField(Field):
    attr_type: re.Pattern = re.Pattern

    def __new__(cls, *args, **kwargs) -> re.Pattern:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        default: Any = None,
        readonly: bool = False,
        required: bool = False,
        required_lazy: bool = False,
        empty_is_none: bool = False,
        **kwargs
    ) -> None:
        super().__init__(default=default, readonly=readonly, required=required, required_lazy=required_lazy, empty_is_none=empty_is_none, **kwargs)


    def clean_value(self, value: Any) -> Any:
        """
        Clean the value by compiling it into a regular expression pattern.

        This method takes a value and compiles it into a regular expression pattern if it is a string.
        If the value is equal to the default value, it updates the default value with the compiled pattern.
        It then calls the parent class's clean_value method to perform any additional cleaning.

        Args:
            value (Any): The value to be cleaned.

        Example Usage:
            >>> field = RegexField(default='[0-9]+')
            >>> field.clean_value('123')
            re.compile('[0-9]+')

        Returns:
            Any: The cleaned value.
        """
        if isinstance(value, str):
            value = re.compile(value)
        return super().clean_value(value)


class TupleField(Field):
    attr_type: tuple = tuple

    def __new__(cls, *args, **kwargs) -> tuple:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)


class URLField(Field):
    attr_type: str = str
    supported_schemes: set = {'http', 'https', 'ftp', 'ftps', 'git'}

    def __new__(cls, *args, **kwargs) -> str:
        # This signature is used to change the default autocomplete for this class
        return super().__new__(cls, *args, **kwargs)

    def validate(self, attr_name: str, value: Any, attr_type: type = None) -> None:
        if isinstance(value, str):
            # https://github.com/django/django/blob/main/django/core/validators.py
            ul = "\u00a1-\uffff"  # Unicode letters range (must not be a raw string).

            # IP patterns
            ipv4_re = (
                r"(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)"
                r"(?:\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}"
            )
            ipv6_re = r"\[[0-9a-f:.]+\]"  # (simple regex, validated later)

            # Host patterns
            hostname_re = (
                r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?"
            )

            # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
            domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
            tld_re = (
                r"\."  # dot
                r"(?!-)"  # can't start with a dash
                r"(?:[a-z" + ul + "-]{2,63}"  # domain label
                r"|xn--[a-z0-9]{1,59})"  # or punycode label
                r"(?<!-)"  # can't end with a dash
                r"\.?"  # may have a trailing dot
            )
            host_re = "(" + hostname_re + domain_re + tld_re + "|localhost)"

            regex = re.compile(
                r"^(?:[a-z0-9.+-]*)://"  # scheme is validated separately
                r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?"  # user:pass authentication
                r"(?:" + ipv4_re + "|" + ipv6_re + "|" + host_re + ")"
                r"(?::[0-9]{1,5})?"  # port
                r"(?:[/?#][^\s]*)?"  # resource path
                r"\Z",
                re.IGNORECASE,
            )
            if not regex.match(value):
                raise FieldValueError(f'Key {attr_name} must be an URL.')

            # The scheme needs a separate check
            try:
                result = urlparse(value)
                if result.scheme not in self.supported_schemes:
                    raise FieldValueError(f'Key {attr_name} must be an URL.')

            except ValueError:
                raise FieldValueError(f'Key {attr_name} must be an URL.') from ValueError

        return super().validate(attr_name, value, attr_type)
