from aiohttp import ClientResponseError
from requests.exceptions import HTTPError


class InstagramException(Exception):
    pass


class InternetException(InstagramException):
    def __init__(self, exception):
        if isinstance(exception, HTTPError):
            super().__init__(
                "Error by connection with Instagram to '%s' with response code '%s'" % (
                    exception.request.url,
                    exception.response.status_code,
                ),
            )
            self.request = exception.request
            self.response = exception.response
        elif isinstance(exception, ClientResponseError):
            super().__init__(
                "Error by connection with Instagram to '%s' with response code '%s'" % (
                    exception.request_info.real_url,
                    exception.status,
                ),
            )


class AuthException(InstagramException):
    def __init__(self, username, message=""):
        super().__init__("Cannot auth user with username '%s': %s" % (username, message))


class CheckpointException(AuthException):
    def __init__(self, username, checkpoint_url, navigation, types):
        super().__init__(username, "need verification by checkpoint")
        self.checkpoint_url = checkpoint_url
        self.navigation = navigation
        self.types = types


class IncorrectVerificationTypeException(AuthException):
    def __init__(self, username, type):
        super().__init__(username, "incorrect verification type '%s'" % type)
        self.type = type


class UnexpectedResponse(InstagramException):
    def __init__(self, exception, url):
        super().__init__("Get unexpected response from '%s'\nError: %s" % (
            url,
            str(exception),
        ))


class NotUpdatedElement(InstagramException):
    def __init__(self, element, argument):
        super().__init__("Element '%s' haven't argument %s. Please, update this element" % (
            element.__repr__(),
            argument,
        ))


class ExceptionManager:
    def __init__(self, repeats=1):
        self.tree = {
            "action": lambda exception, *args, **kwargs: (args, kwargs),
            "branch": {},
        }
        self.repeats = repeats


    def __getitem__(self, key):
        if not issubclass(key, Exception):
            raise TypeError("Key must be Exception type")

        return self.search(key)[0]["action"]


    def search(self, exception):
        if not issubclass(exception, Exception):
            raise TypeError("'exception' must be Exception type")

        current = self.tree
        while True:
            for key, value in current["branch"].items():
                if key == exception:
                    return value, True
                elif issubclass(exception, key):
                    current = value
                    break
            else:
                return current, False
            continue


    def __setitem__(self, key, value):
        if not issubclass(key, Exception):
            raise TypeError("Key must be Exception type")
        if not callable(value):
            raise TypeError("Value must be function")

        item, exists = self.search(key)
        if exists:
            item["action"] = value
        else:
            item["branch"][key] = {"branch": {}, "action": value}


    def decorator(self, func):
        def wrapper(obj, *args, **kwargs):
            for _ in range(self.repeats):
                try:
                    return func(obj, *args, **kwargs)
                except Exception as e:
                    exception = e
                    args, kwargs = self[exception.__class__](exception, *args, **kwargs)
            else:
                raise exception

        return wrapper