# coding: utf-8

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import re
import requests
import logging

from enum import Enum

from six.moves.collections_abc import Sequence
from six import string_types, iteritems, itervalues

from .errors import ProjectNetworksApiError, UnknownMacro
from .network_macro import NetworkMacro


class Operations(Enum):
    list_macros = 'list_macros', 'GET'
    show_macro = 'show_macro', 'GET'
    list_ranges = 'list_ranges', 'GET'
    create_macro = 'create_macro', 'POST'
    edit_macro = 'edit_macro', 'POST'
    delete_macro = 'delete_macro', 'POST'
    create_network = 'create_network', 'POST'
    delete_network = 'delete_network', 'POST'
    move_network = 'move_network', 'POST'


class ProjectNetworksApi(object):
    LOGGER = logging.getLogger(__name__)
    BASE_URL = 'https://racktables.yandex-team.ru/export/project-id-networks.php'
    _ERROR_REGEXP = re.compile('Record \'macro\'#\'(?P<macro>.*)\' does not exist')

    def __init__(self, oauth_token=None):
        if oauth_token is None:
            from saas.library.python.token_store import TokenStore
            oauth_token = TokenStore.get_token_from_store_or_env('racktables')
        headers = {'Authorization': 'OAuth {}'.format(oauth_token)}

        self._session = requests.Session()
        self._session.headers.update(headers)

    def _make_request(self, operation, **kwargs):
        kwargs['op'] = operation.name
        params = {k: v for k, v in iteritems(kwargs) if v is not None}
        try:
            response = self._session.request(operation.value[1], self.BASE_URL, params=params)
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as e:
            self.LOGGER.exception('Racktables request %s with params %s failed', operation.name, params, exc_info=e)
            if e.response.status_code == requests.codes.server_error and self._ERROR_REGEXP.match(e.response.text):
                raise UnknownMacro(inner_exception=e)
            else:
                raise ProjectNetworksApiError(inner_exception=e)
        except requests.RequestException as e:
            self.LOGGER.exception('Racktables request %s with params %s failed', operation.name, params, exc_info=e)
            raise ProjectNetworksApiError(inner_exception=e)

    @staticmethod
    def _normalise_owners(owners):
        if isinstance(owners, string_types):
            owners_str = owners
        elif isinstance(owners, Sequence):
            owners_str = ','.join(owners)
        elif owners is None:
            owners_str = None
        else:
            raise ValueError('owners must be string or Sequence of strings')
        return owners_str

    def list_macros(self):
        return [NetworkMacro(**v) for v in itervalues(self._make_request(Operations.list_macros))]

    def show_macro(self, name):
        return NetworkMacro(**self._make_request(Operations.show_macro, name=name))

    def macro_exists(self, name):
        try:
            m = self.show_macro(name)
            return m
        except UnknownMacro:
            return False

    def list_ranges(self):
        return self._make_request(Operations.list_ranges)

    def create_macro(self, name, owner_service, owners, parent, description, secured=None):
        owners_str = self._normalise_owners(owners)
        return NetworkMacro(**self._make_request(Operations.create_macro, name=name, owners=owners_str, owner_service=owner_service, parent=parent, description=description, secured=secured))

    def edit_macro(self, name, owner_service=None, owners=None, parent=None, description=None):
        if owner_service is None and owners is None and parent is None and description is None:
            raise ValueError('At least one attribute must be changed')
        owners_str = self._normalise_owners(owners)
        return NetworkMacro(**self._make_request(Operations.edit_macro, name=name, owner_service=owner_service, owners=owners_str, parent=parent, description=description))

    def delete_macro(self, name):
        return self._make_request(Operations.delete_macro, name=name)['warnings']

    def create_network(self, macro_name, scope=None, project_id=None, description=None):
        return NetworkMacro(**self._make_request(Operations.create_network, macro_name=macro_name, scope=scope, project_id=project_id, description=description))

    def delete_network(self, project_id, macro_name=None, scope=None):
        return NetworkMacro(**self._make_request(Operations.delete_network, project_id=project_id, macro_name=macro_name, scope=scope))

    def move_network(self, project_id, macro_name, scope=None):
        return NetworkMacro(**self._make_request(Operations.move_network, project_id=project_id, macro_name=macro_name, scope=scope))
