from __future__ import annotations

import base64
from io import BytesIO
from typing import Any, Tuple

import webcolors
from PIL import Image
from werkzeug.datastructures import FileStorage


def is_integer(contender: Any) -> bool:
    """
    Determine if a string (or other object type that can be converted automatically) represents an integer.

    Thanks to https://note.nkmk.me/en/python-check-int-float/.

    :param contender: object to test
    :return: true if it's an integer
    """
    try:
        float(contender)
    except ValueError:
        return False
    else:
        return float(contender).is_integer()


def thumbnail_uploaded_image(file: FileStorage, max_size: Tuple[int, int] = (256, 256)) -> bytes:
    """
    Process an uploaded image file into a resized image of a specific size.

    :param file: An uploaded image file.
    :param max_size: A tuple containing the maximum width and height for the thumbnail image. Default is (256, 256).
    :return: The byte data of the thumbnail image.
    """
    if file.filename is None:
        raise ValueError("No file was uploaded")
    img = Image.open(file)
    img.thumbnail(max_size)
    byte_arr = BytesIO()
    img.save(byte_arr, format='PNG' if file.filename.lower().endswith('.png') else 'JPEG')
    return byte_arr.getvalue()


def create_data_uri(bytes_data: bytes, file_extension: str) -> str:
    """
    Create a data URI from binary data and a file extension.

    :param bytes_data: The binary data of an image.
    :param file_extension: The file extension of the image.
    :return: A data URI representing the image.
    """
    # base64 encode
    encoded = base64.b64encode(bytes_data).decode('ascii')
    # create data URI
    data_uri = "data:image/{};base64,{}".format('jpeg' if file_extension == 'jpg' else file_extension, encoded)
    return data_uri


def normalize_color(color: str) -> str:
    """
    Normalize a string representing a color to its hexadecimal representation.

    This function accepts a string representing a color in one of the following formats:

    - A CSS color name, such as 'red', 'green', or 'blue'.
    - A 6-digit hexadecimal color code, such as '#FF0000' for red.
    - A 3-digit hexadecimal color code, such as '#F00' for red.

    The function returns a string with the color in 6-digit or 3-digit hexadecimal format,
    with lowercase letters. If the color is given as a CSS color name, the function
    returns its equivalent 6-digit hexadecimal format.

    :param color: A string representing a color.
    :return: The color in 6-digit or 3-digit hexadecimal format.
    :raises: ValueError: If the input string does not represent a valid color in any of the accepted formats.
    """
    try:
        return webcolors.name_to_hex(color)  # type: ignore[no-any-return]
    except ValueError:
        pass
    if color.startswith('#'):
        color = color[1:].lower()
    if len(color) in [3, 6]:
        try:
            _ = int(color, 16)
            return f"#{color}"
        except ValueError:
            pass
    raise ValueError(f"color must be a valid HTML color, got: {repr(color)}")
