import re
import uuid

from PIL import Image
from rest_framework import serializers


def name_validator(name, min_length, max_length):
    """
    This function checks if the incoming first name is valid or not!
    it should has 2 to 46 characters.
    """
    # if len(name) < min_length or len(name) > max_length:
    #     raise serializers.ValidationError(f'Enter a valid name. It should be between {min_length} and {max_length} characters')
    if not re.match(f'^[\u0600-\u06FFa-zA-Z.0-9\-،,_()\u200c ]{{{min_length},{max_length}}}$',
                    name):
        raise serializers.ValidationError(
            f'Enter a valid name. it can be english and persian characters, numbers and dot, underline or space and also between {min_length} and {max_length} characters')
    return name


def image_validetor(image, image_size_as_MB, format, max_side):
    if not image.name.endswith(format):
        raise serializers.ValidationError(
            f'The image format must be {", ".join([item[1:] for item in format])}')
    if image.size > (1024 * 1024 * image_size_as_MB):
        raise serializers.ValidationError(
            f'The maximum image size must be {image_size_as_MB} MB')

    im = Image.open(image)
    height = im.height
    width = im.width
    if height > max_side or width > max_side:
        raise serializers.ValidationError(
            f'The maximum image side size must be {max_side} px')

    return image


def uuid_checker(incoming_id):
    try:
        uuid.UUID(incoming_id, version=4)
    except:
        return False
    return True


def uuid_validator(incoming_id):
    if uuid_checker(incoming_id):
        return incoming_id
    raise serializers.ValidationError('Enter a valid id')


# check_for_required_and_optional_fields(box, ['id', 'satisfaction', ...], ['delete_on_server', ...], 'boxes', 'boxes', True)
# def check_for_required_and_optional_fields(data, required_items, optional_items, item_parent_name, error_key,
#                                            parent_is_array):
#     for item in required_items:
#         if item not in data:
#             if parent_is_array:
#                 if error_key:
#                     raise serializers.ValidationError(
#                         {
#                             error_key: f"'{item}' was not found in one of the items in {item_parent_name}. Each item in {item_parent_name} must contain {', '.join(required_items)}."})
#                 raise serializers.ValidationError(
#                     f"'{item}' was not found in one of the items in {item_parent_name}. Each item in {item_parent_name} must contain {', '.join(required_items)}.")
#             else:
#                 if error_key:
#                     raise serializers.ValidationError(
#                         {
#                             error_key: f"'{item}' was not found in {item_parent_name}. {item_parent_name} must contain {', '.join(required_items)}."})
#                 raise serializers.ValidationError(
#                     f"'{item}' was not found in {item_parent_name}. {item_parent_name} must contain {', '.join(required_items)}.")
#     for item in data:
#         if item not in required_items and item not in optional_items:
#             if parent_is_array:
#                 if error_key:
#                     raise serializers.ValidationError(
#                         {
#                             error_key: f"'{item}' is not allowed in {item_parent_name} children. Each item in {item_parent_name} must only contain {', '.join(required_items + optional_items)}."})
#                 raise serializers.ValidationError(
#                     f"'{item}' is not allowed in {item_parent_name} children. Each item in {item_parent_name} must only contain {', '.join(required_items + optional_items)}.")
#             else:
#                 if error_key:
#                     raise serializers.ValidationError(
#                         {
#                             error_key: f"'{item}' is not allowed in {item_parent_name}. Each item in {item_parent_name} must only contain {', '.join(required_items + optional_items)}."})
#                 raise serializers.ValidationError(
#                     f"'{item}' is not allowed in {item_parent_name}. Each item in {item_parent_name} must only contain {', '.join(required_items + optional_items)}.")

def dic_validetor(data, error_key, container_name, raise_error_for_unexpected_key=False, many=False, required_items=[], optional_items=[]):
    def type_validetor(value, expected_type):
        if expected_type is float:
            return type(value) is float or type(value) is int
        else:
            return type(value) is expected_type

    def raise_error(key, unexpected_key=False, error_message=None):

        if error_message:
            raise serializers.ValidationError({error_key: error_message})

        else:
            required_keys_info = [
                f"{item['key']}({item['type'].__name__})*" for item in required_items]
            optional_keys_info = [
                f"{item['key']}({item['type'].__name__})" for item in optional_items]

            keys_info = [*required_keys_info, *optional_keys_info]

            prefix = ''
            suffix = ''

            if unexpected_key:
                prefix = f"Unexpected key '{key}'."
            elif many:
                prefix = f"valid '{key}' was not found in one of the items in {container_name}."
            else:
                prefix = f"valid '{key}' was not found in {container_name}."

            if many:
                suffix = f"Each item in {container_name} must {'only ' if raise_error_for_unexpected_key else ''}contain {', '.join(keys_info)}."
            else:
                suffix = f"{container_name} must {'only ' if raise_error_for_unexpected_key else ''}contain {', '.join(keys_info)}."

            raise serializers.ValidationError(
                {error_key: f"{prefix} {suffix}"})

    for item in required_items:

        key = item.get('key')
        value_type = item.get('type')
        validator = item.get('validator')
        validator_error_message = item.get('validator_error_message')

        if key not in data or not type_validetor(data[key], value_type):
            raise_error(key)
        elif validator and not validator(data[key]):

            raise_error(key, error_message=validator_error_message)

    for item in optional_items:

        key = item.get('key')
        value_type = item.get('type')
        validator = item.get('validator')
        validator_error_message = item.get('validator_error_message')

        if key in data and data[key] is not None:
            if not type_validetor(data[key], value_type):
                raise_error(key)
            elif validator and not validator(data[key]):
                raise_error(key, error_message=validator_error_message)

    if raise_error_for_unexpected_key:
        for key in data:
            if key not in [item['key'] for item in required_items] and key not in [item['key'] for item in optional_items]:
                raise_error(key, unexpected_key=True)


# we have two options for choice validation
# deleted_filter = serializers.CharField(validators=[IncludeExcludeOnlyValidator()])
# deleted_filter = serializers.ChoiceField(choices=...)
# the second option does not return a good error message
# error message example:  "non_existence_filter" is not a valid choice.
# our method returns errors like: auto_correctable_filter should be one of include, exclude, only


class IncludeExcludeOnlyValidator:
    requires_context = True

    def __call__(self, value, serializer_field):
        allowed_filter_items = ['include', 'exclude', 'only']
        if value not in allowed_filter_items:
            raise serializers.ValidationError(
                f'{serializer_field.field_name} should be one of {", ".join(allowed_filter_items)}')


def active_filter_validator(value):
    allowed_filter_items = ['present', 'future', 'past']
    if value not in allowed_filter_items:
        raise serializers.ValidationError(
            f'active_filter should be one of {", ".join(allowed_filter_items)}')
    return value


def dict_validator(value):
    if type(value) is not dict:
        raise serializers.ValidationError('should be a valid dict.')
    return value
