import functools
import json
import logging
import os
import random
import ssl
import time

import grpc
import yaml
from enum import Enum
from requests.exceptions import RequestException

from load.projects.cloud.tank_client.exceptions import LoadTestingFailedPreconditionError, LoadTestingInternalError, \
    LoadTestingNotFoundError, LoadTestingConnectionError, JWTError, ObjectStorageError

LOGGING_PATH = os.environ.get('TANK_CLIENT_LOGGING_PATH') or 'tank_client_run.log'
LOGGER = logging.getLogger(__file__)


class TankStatus(Enum):
    STATUS_UNSPECIFIED = 0
    READY_FOR_TEST = 1
    PREPARING_TEST = 2
    TESTING = 3
    TANK_FAILED = 4


class LogType(Enum):
    UNKNOWN = 0
    TANK = 1
    PHANTOM = 2
    PANDORA = 3


class Generator(Enum):
    UNKNOWN = 0
    PHANTOM = 1
    PANDORA = 2


def get_creds(host=None, port=None):
    if host is not None:
        cert = ssl.get_server_certificate((host, port))
        creds = grpc.ssl_channel_credentials(cert.encode('utf-8'))
    else:
        creds = grpc.ssl_channel_credentials()
    return creds


def get_call_creds(token):
    return grpc.access_token_call_credentials(token)


def write_config_file(config: dict, filepath):
    with open(filepath, 'w') as f:
        yaml.dump(config, f)
    LOGGER.debug('Config was written to %s', filepath)


def retry(logger, max_delay, max_count):
    def wrapper_func(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_count + 1):
                try:
                    return func(*args, **kwargs)
                except (LoadTestingConnectionError, RequestException, ObjectStorageError, JWTError):
                    logger.warning('Connection problems', exc_info=True)
                    if attempt < max_count:
                        multiplier = random.uniform(1, 1.5)
                        time.sleep(min(2 ** attempt * multiplier, max_delay))
                        logger.debug(f'Retry attempt #{attempt + 1}')
                    else:
                        raise

        return wrapper

    return wrapper_func


def catch_exceptions(logger=None, return_data=None):
    def wrapper_func(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except RequestException as error:
                logger.error(error)
            except grpc.RpcError as error:
                logger.error(error.details(), error.code())
                if error.code() in (grpc.StatusCode.UNAVAILABLE, grpc.StatusCode.DEADLINE_EXCEEDED):
                    raise LoadTestingConnectionError()
                if error.code() == grpc.StatusCode.NOT_FOUND:
                    raise LoadTestingNotFoundError()
                if error.code() == grpc.StatusCode.INTERNAL:
                    raise LoadTestingInternalError()
                if error.code() == grpc.StatusCode.FAILED_PRECONDITION:
                    raise LoadTestingFailedPreconditionError()
                raise error
            except json.JSONDecodeError as error:
                logger.error(f'Could not serialize response to json: {error}')
                raise
            except Exception as error:
                logger.exception(f'Unexpected error: {type(error)} {error}')
                raise
            raise RuntimeError("'return_data' is deprecated!")

        return wrapper

    return wrapper_func


LOGGING = {
    "version": 1,
    "handlers": {
        "fileHandler": {
            "class": "logging.FileHandler",
            "formatter": "loggerFormatter",
            "filename": LOGGING_PATH
        }
    },
    "loggers": {
        "tank_client": {
            "handlers": ["fileHandler"],
            "level": "DEBUG",
        }
    },
    "formatters": {
        "loggerFormatter": {
            "format": "%(asctime)s - [%(levelname)s] - %(filename)s:%(lineno)d - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S"
        }
    }
}
