import os
from typing import Callable, Optional, Sequence, TypeVar

from rest_framework.exceptions import ValidationError


def cast_bool_from_str(value):
    if isinstance(value, str):
        if value.lower() in ['true', 'yes', 'on', '1']:
            value = True
        elif value.lower() in ['false', 'no', 'not', 'off', '0']:
            value = False
        else:
            raise ValueError(f'Incorrect bool value "{value}". ' f'It should be one of [1, 0, true, false, yes, no]')
    return value


def bool_from_request(params, key, default):
    """Get boolean value from request GET, POST, etc

    :param params: dict POST, GET, etc
    :param key: key to find
    :param default: default value
    :return: boolean
    """
    value = params.get(key, default)

    try:
        if isinstance(value, str):
            value = cast_bool_from_str(value)
        return bool(int(value))
    except Exception as e:
        raise ValidationError({key: str(e)})


def int_from_request(params, key, default):
    """Get integer from request GET, POST, etc

    :param params: dict POST, GET, etc
    :param key: key to find
    :param default: default value
    :return: int
    """
    value = params.get(key, default)

    # str
    if isinstance(value, str):
        try:
            return int(value)
        except ValueError:
            raise ValidationError({key: f'Incorrect value in key "{key}" = "{value}". It should be digit string.'})
        except Exception as e:
            raise ValidationError({key: str(e)})
    # int
    elif isinstance(value, int):
        return value
    # other
    else:
        raise ValidationError(
            {key: f'Incorrect value type in key "{key}" = "{value}". ' f'It should be digit string or integer.'}
        )


def float_from_request(params, key, default):
    """Get float from request GET, POST, etc

    :param params: dict POST, GET, etc
    :param key: key to find
    :param default: default value
    :return: float
    """
    value = params.get(key, default)

    # str
    if isinstance(value, str):
        try:
            return float(value)
        except ValueError:
            raise ValidationError({key: f'Incorrect value in key "{key}" = "{value}". It should be digit string.'})
    # float
    elif isinstance(value, float) or isinstance(value, int):
        return float(value)
    # other
    else:
        raise ValidationError(
            {key: f'Incorrect value type in key "{key}" = "{value}". ' f'It should be digit string or float.'}
        )


def list_of_strings_from_request(params, key, default):
    """Get list of strings from request GET, POST, etc

    :param params: dict POST, GET, etc
    :param key: key to find
    :param default: default value
    :return: float
    """
    value = params.get(key, default)
    if value is None:
        return
    splitters = (',', ';', '|')
    # str
    if isinstance(value, str):
        for splitter in splitters:
            if splitter in value:
                return value.split(splitter)
        return [value]
    else:
        raise ValidationError(
            {key: f'Incorrect value type in key "{key}" = "{value}". ' f'It should be digit string or float.'}
        )


def get_env(name, default=None, is_bool=False):
    for env_key in ['LABEL_STUDIO_' + name, 'HEARTEX_' + name, name]:
        value = os.environ.get(env_key)
        if value is not None:
            if is_bool:
                return bool_from_request(os.environ, env_key, default)
            else:
                return value
    return default


def has_env(name: str) -> bool:
    """Return True if any supported environment variable name is set for ``name``."""
    return any((prefix + name) in os.environ for prefix in ('LABEL_STUDIO_', 'HEARTEX_', ''))


def get_bool_env(key, default):
    return get_env(key, default, is_bool=True)


T = TypeVar('T')


def get_env_list(
    key: str, default: Optional[Sequence[T]] = None, value_transform: Callable[[str], T] = str
) -> Sequence[T]:
    """
    "foo,bar,baz" in env variable => ["foo", "bar", "baz"] in python.
    Use value_transform to convert the strings to any other type.
    """
    value = get_env(key)
    if not value:
        if default is None:
            return []
        return default

    return [value_transform(el) for el in value.split(',')]


def get_env_list_int(key, default=None) -> Sequence[int]:
    return get_env_list(key, default=default, value_transform=int)


def get_all_env_with_prefix(prefix=None, is_bool=True, default_value=None):
    out = {}
    for key in os.environ.keys():
        if not key.startswith(prefix):
            continue
        if is_bool:
            out[key] = bool_from_request(os.environ, key, default_value)
        else:
            out[key] = os.environ[key]
    return out
