import base64
import os
import platform
from collections import OrderedDict
from io import BytesIO
from pathlib import Path
from typing import Sequence

import requests
from PIL import Image


class LRUCache:
    # initializing capacity
    def __init__(self, capacity: int):
        self.cache = OrderedDict()
        self.capacity = capacity

    def has(self, key) -> bool:
        return key in self.cache

    # we return the value of the key
    # that is queried in O(1) and return -1 if we
    # don't find the key in out dict / cache.
    # And also move the key to the end
    # to show that it was recently used.
    def get(self, key, default=None):
        if key not in self.cache:
            return default
        else:
            self.cache.move_to_end(key)
            return self.cache[key]

    # first, we add / update the key by conventional methods.
    # And also move the key to the end to show that it was recently used.
    # But here we will also check whether the length of our
    # ordered dictionary has exceeded our capacity,
    # If so we remove the first key (least recently used)
    def put(self, key, value) -> None:
        self.cache[key] = value
        self.cache.move_to_end(key)
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)

    def pop(self, key, value):
        self.cache.pop(key, None)


def handle_response(res, json, url):
    if res.status_code < 299:
        return res.json()
    elif res.status_code == 404:
        logging.error("Request URL: {} | Error[404]: 请求错误: 错误的地址".format(url))
        raise VQLError(516)
    elif res.status_code == 422:
        logging.error(
            "Request URL: {} | Request body: {} | Error[422]: 请求错误: 错误的请求格式".format(
                url, json
            )
        )
        raise VQLError(517)
    else:
        info = res.json()
        logging.error(
            "Request URL: {} | Request body: {} | Error: {}".format(url, json, info)
        )
        raise VQLError(511, detail=info)


def chunks(l: Sequence, win_len: int, stride_len: int):
    s_id = 0
    e_id = min(len(l), win_len)

    while True:
        yield l[s_id:e_id]

        if e_id == len(l):
            break

        s_id = s_id + stride_len
        e_id = min(s_id + win_len, len(l))


def encode_image(input):
    def _encode(img):
        output_buffer = BytesIO()
        img.save(output_buffer, format="JPEG")
        byte_data = output_buffer.getvalue()
        base64_str = base64.b64encode(byte_data)
        base64_str = str(base64_str, encoding="utf-8")
        return base64_str

    input = input.convert("RGB")
    if isinstance(input, list):
        res = []
        for img in input:
            res.append(_encode(img))
        return res
    else:
        return _encode(input)


def get_platform() -> str:
    """Get platform."""
    system = platform.system()
    if system == "Darwin":
        return "MacOS"
    return system


def read_image(input_source) -> Image.Image:
    """
    Read an image from a local path, URL, PIL Image object, or Path object.

    Args:
        input_source (str or PIL.Image.Image or Path): The source of the image.
            Can be a local file path, a URL, a PIL Image object, or a Path object.

    Returns:
        PIL.Image.Image: The image as a PIL Image object.

    Raises:
        ValueError: If the input source is invalid or the image cannot be read.
    """
    if isinstance(input_source, Image.Image):
        return input_source

    if isinstance(input_source, (str, Path)):
        if isinstance(input_source, str) and input_source.startswith(
            ("http://", "https://")
        ):
            # URL
            try:
                response = requests.get(input_source)
                response.raise_for_status()
                return Image.open(BytesIO(response.content))
            except requests.RequestException as e:
                raise ValueError(f"Failed to fetch image from URL: {e}")
        elif os.path.isfile(str(input_source)):
            # Local file path or Path object
            try:
                return Image.open(input_source)
            except IOError as e:
                raise ValueError(f"Failed to open local image file: {e}")
        else:
            raise ValueError(
                "Invalid input source. Must be a valid URL or local file path."
            )

    raise ValueError(
        "Invalid input type. Must be a string (URL or file path), Path object, or PIL Image object."
    )