# -*- coding: utf-8 -*-
"""
Wrapper of http mds avatars methods
https://wiki.yandex-team.ru/mds/avatars/
"""
import logging
import json

import requests

log = logging.getLogger(__name__)

INTERFACES = {
    'production': {
        # FIXME: будет понятно когда доберемся до продакшна
        'write': 'avatars-int.mds.yandex.net:13000',
        # 'read': 'storage-int.mds.yandex.net',  # (80/443)
        'public': 'avatars.mds.yandex.net',  # (80/443)
    },
    'testing': {
        'write': 'avatars-int.mdst.yandex.net:13000',
        # 'read': 'avatars-int.mdst.yandex.net',  # (80/443)      # FIXME: не уверен что адрес именно такой
        'public': 'avatars.mdst.yandex.net',  # (80/443)
    },
}

ERROR_CODES = {
    400: 'Wrong query',
    401: 'No auth header',
    403: 'Filename already exists. Update disabled',
    404: 'No data for key',
    406: 'External storage does not know namespace',
    410: 'Internal storage does not know namespace',
    507: 'Not enough free space',
}

TIMEOUT_DEFAULT = 3


def force_decode(content, codecs=('windows-1251', 'utf-8')):
        for codec in codecs:
            try:
                return content.decode(codec)
            except UnicodeDecodeError:
                pass


class MdsError(Exception):
    pass


class MdsKeyNotFound(MdsError):
    pass


class MdsFilenameAlreadyExists(MdsError):
    def __init__(self, key, *args, **kwargs):
        super(MdsFilenameAlreadyExists, self).__init__(key, *args, **kwargs)
        self.key = key


class MdsClient(object):
    def __init__(self, namespace, env='production', interface=None, timeout=None):
        self._namespace = namespace
        self._interface = interface or INTERFACES[env]
        self._timeout = timeout

    def write(self, filename, data, expire_at, timeout=None):
        """ Запись изображения в сторадж

        Хранилище не является чистым key-value storage и не гарантирует уникальность ключей.
        Пользователь должен самостоятельно гарантировать уникальность filename при загрузке данных.

        https://wiki.yandex-team.ru/mds/avatars/#put

        :param str|unicode filename: пользовательское имя файла
        :param str|bytes data: данные для записи
        :param timeout: таймаут на чтение ответа от сервера
        :param str expire_at:
        :return: идентификатор ресура в формате "couple/filename"
        """
        retries = 3
        exception_text = ''
        while --retries:
            try:
                return write(
                    filename, data, self._namespace,
                    host=self._interface['write'],
                    public_host=self._interface['public'],
                    expire_at=expire_at,
                    timeout=timeout or self._timeout
                )
            except requests.exceptions.Timeout as e:
                exception_text = e
            return 'Exception: {0}'.format(str(exception_text))

        # def read(self, key, timeout=None):
        #     """ Чтение объекта из стораджа
        #
        #     https://wiki.yandex-team.ru/mds/dev/protocol/#get
        #
        #     :param str|unicode key: идентификатор ресура ("couple/filename")
        #     :param timeout: таймаут на чтение ответа от сервера
        #     :return str|bytes : данные которые содержатся в файле
        #     """
        #     return read(
        #         key, self._namespace,
        #         host=self._interface['read'],
        #         timeout=timeout or self._timeout
        #     )
        #
        # def delete(self, filename, timeout=None):
        #     """ Удаление ресурса из стораджа
        #
        #     Сторадж возвращает http 200 только тогда,
        #     когда данные были удалены со всех реплик
        #
        #     https://wiki.yandex-team.ru/mds/dev/protocol/#delete
        #
        #     :param str|unicode filename: идентификатор ресура ("couple/filename")
        #     :param timeout: таймаут на чтение ответа от сервера
        #     :return:
        #     """
        #     return delete(
        #         filename, self._namespace,
        #         host=self._interface['write'],
        #         timeout=timeout or self._timeout
        #     )
        #
        # def is_exists(self, key, timeout=None):
        #     """ Проверка существования ресурса в сторадже
        #
        #     не происходит запроса контента, только заголовки
        #
        #     https://wiki.yandex-team.ru/mds/dev/protocol/#headzaprosvsegoobekta
        #
        #     :param str|unicode key: идентификатор ресура ("couple/filename")
        #     :param int timeout: таймаут на чтение ответа от сервера
        #     :return bool:
        #     """
        #
        #     return exists(
        #         key, self._namespace,
        #         host=self._interface['read'],
        #         timeout=timeout or self._timeout
        #     )


def write(imagename, data, namespace, host, public_host, expire_at, timeout=None):
    url = 'http://{host}/put-{ns}/{imagename}?expire-at={expire_at}'.format(
        host=host, ns=namespace, imagename=imagename, expire_at=expire_at
    )

    r = requests.post(
        url,
        files={'file': ('file', data)},
        # data=data,
        headers={'Content-Disposition': 'filename'},
        timeout=timeout or TIMEOUT_DEFAULT,
        verify=False
    )

    content = force_decode(r.content)

    if r.ok:
        geoup, sizes = parse_group_and_sizes_from_mds_upload_response(content, public_host)
        log.debug(
            'Upload: %s %s bytes. Answer: %s',
            r.request.url, len(r.request.body), content
        )
        log.info('Upload key: %r', geoup)

        return sizes

    else:
        log.debug(
            'Upload error: %s %s bytes. Answer: %s',
            r.request.url, len(r.request.body), content
        )

        if r.status_code == 403:
            print(content)
            raise MdsFilenameAlreadyExists(parse_group_and_sizes_from_mds_upload_response(content, public_host)[0])

        error = error_code_interpretation(r.status_code)

        raise MdsError('{} {} {}'.format(r.status_code, error, content))


def parse_group_and_sizes_from_mds_upload_response(content, public_host):
    def full_path(file_path):
        return 'https://{public_host}{file_path}'.format(public_host=public_host, file_path=file_path)
    parsed_content = json.loads(content)
    if 'group-id' in parsed_content and 'sizes' in parsed_content:
        files = [(size, full_path(file['path'])) for size, file in parsed_content['sizes'].items()]
        return parsed_content['group-id'], files
    else:
        return None, None


#
# def parse_xml_post_key(xml):
#     return etree.fromstring(xml).find('key').text
#
#
# def read(key, namespace, host, timeout=None):
#     url = 'http://{host}/get-{ns}/{key}'.format(
#         host=host, ns=namespace, key=key
#     )
#
#     r = requests.get(
#         url,
#         timeout=timeout or TIMEOUT_DEFAULT,
#         verify=False
#     )
#
#     if r.ok:
#         log.debug('Read: %s %s bytes', r.request.url, len(r.content))
#         return r.content
#
#     else:
#         log.debug('Read error: %s %s', r.request.url, r.content)
#
#         error = error_code_interpretation(r.status_code)
#         raise MdsError('{} {} {}'.format(r.status_code, error, r.content))
#
#
# def exists(key, namespace, host, timeout=None):
#     url = 'http://{host}/get-{ns}/{key}'.format(
#         host=host, ns=namespace, key=key
#     )
#
#     r = requests.head(
#         url,
#         timeout=timeout or TIMEOUT_DEFAULT,
#         verify=False
#     )
#
#     if r.ok:
#         log.debug('Exists: %s %s bytes', r.request.url, r.headers['Content-Length'])
#         return True
#     elif r.status_code == 404:
#         log.debug('Not exists: %s', r.request.url)
#         return False
#     else:
#         log.debug('Exists error: %s %s', r.request.url, r.content)
#
#         error = error_code_interpretation(r.status_code)
#         raise MdsError('{} {} {}'.format(r.status_code, error, r.content))
#
#
# def delete(key, namespace, host, timeout=None):
#     url = 'http://{host}/delete-{ns}/{key}'.format(
#         host=host, ns=namespace, key=key
#     )
#
#     r = requests.get(
#         url,
#         timeout=timeout or TIMEOUT_DEFAULT,
#         verify=False
#     )
#
#     if r.ok:
#         log.debug('Delete: %s %s', r.request.url, r.content)
#         return
#
#     log.debug('Delete error: %s %s', r.request.url, r.content)
#
#     if r.status_code == 404:
#         raise MdsKeyNotFound(r.content)
#
#     else:
#         error = error_code_interpretation(r.status_code)
#         raise MdsError('{} {} {}'.format(r.status_code, error, r.content))


def error_code_interpretation(code):
    """Получение описания ошибк по ее http коду

    :param code: http код ошибки  от mds
    :return:
    """
    code = int(code)

    if code in ERROR_CODES:
        return ERROR_CODES[code]
    elif code >= 500:
        return 'Internal server error'
    else:
        return 'Undocumented'
