# Standard library imports
import base64
import io
import os
import urllib

# Third-party imports
import pi_heif  # type: ignore[import-untyped]
import pillow_avif  # type: ignore[import-untyped]
import requests
import yaml
from PIL import Image

pi_heif.register_heif_opener(thumbnails=False)  # Register for HEIF/HEIC
pillow_avif = pillow_avif  # Reference pillow_avif to not remove import by accident


def check_image_path(image_path):
    """
    Check whether a local OR hosted image path is valid
    :param image_path: local path or URL of image
    :returns: Boolean
    """
    return os.path.exists(image_path) or check_image_url(image_path)


def check_image_url(url):
    """
    Check whether a hosted image path is valid
    :param url: URL of image
    :returns: Boolean
    """
    if urllib.parse.urlparse(url).scheme not in ("http", "https"):
        return False

    r = requests.head(url)
    return r.status_code == requests.codes.ok


def mask_image(image, encoded_mask, transparency=60):
    """
    Overlay a translucent mask on top of an image with CV2
    :param image: a CV2 image / numpy.ndarray matrix
    :param encoded_mask: a base64 encoded single channel image
    :param transparency: alpha transparency of masks for semantic overlays
    :returns: CV2 image / numpy.ndarray matrix
    """
    import cv2
    import numpy as np

    np_data = np.fromstring(base64.b64decode(encoded_mask), np.uint8)  # type: ignore[no-overload]
    mask = cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED)

    # Fallback in case the API returns an incorrectly sized mask
    # This should not happen in practice, but we saw it in testing
    image_size = image.shape[1::-1]
    mask_size = mask.shape[1::-1]
    if mask_size != image_size:
        mask = cv2.resize(mask, image_size)

    # Mask the original image
    masked = cv2.bitwise_and(image, image, mask=mask)

    # Overlay a translucent version of the original image
    # on top of the masked image to give it a translucent effect
    alpha = transparency / 100
    return cv2.addWeighted(masked, alpha, image, 1 - alpha, 0)


def validate_image_path(image_path):
    """
    Validate whether a local OR hosted image path is valid
    :param image_path: local path or URL of image
    :returns: None
    :raises Exception: Image path is not valid
    """
    if not check_image_path(image_path):
        raise Exception(f"Image does not exist at {image_path}!")


def file2jpeg(image_path):
    import cv2

    # OpenCV will handle standard formats efficiently
    img = cv2.imread(image_path)
    if img is not None:
        # Convert BGR to RGB for PIL
        image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        pilImage = Image.fromarray(image)
    else:
        # If OpenCV fails, the format might be HEIC/AVIF which are handled by PIL
        pilImage = Image.open(image_path)
        if pilImage.mode != "RGB":
            pilImage = pilImage.convert("RGB")

    buffered = io.BytesIO()
    pilImage.save(buffered, quality=100, format="JPEG")
    return buffered.getvalue()


def load_labelmap(f):
    if f.lower().endswith(".yaml") or f.lower().endswith(".yml"):
        with open(f) as file:
            data = yaml.safe_load(file)
            names = data.get("names", [])
            if isinstance(names, dict):
                return {int(k): v for k, v in names.items()}
            else:
                return {i: name for i, name in enumerate(names)}
    else:
        with open(f) as file:
            lines = [line for line in file.readlines() if line.strip()]
        return {i: line.strip() for i, line in enumerate(lines)}
