# coding: utf-8

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

import logging
import requests

from enum import Enum
from six import iteritems
from six.moves.urllib.parse import urljoin

from saas.library.python.token_store import TokenStore

from .enums import RequestStatus, RuleSourceSystem
from .rule import Rule
from .rule_request import RuleRequest
from .errors import PuncherClientError, RuleAlreadyExists


class PuncherApi(object):
    LOGGER = logging.getLogger(__name__)

    class Endpoints(Enum):
        production = 'https://api.puncher.yandex-team.ru/api/dynfw/'
        testing = 'https://dynfw-test.yandex-team.ru/api/dynfw/'
        development = 'http://dynfw-dev.mon.yandex-team.ru/api/dynfw/'

    def __init__(self, endpoint=Endpoints.production, oauth_token=None):
        if oauth_token is None:
            oauth_token = TokenStore.get_token_from_store_or_env('puncher')
        self.LOGGER.info('Using token %s', TokenStore.mask_token(oauth_token))
        headers = {'Authorization': 'OAuth {}'.format(oauth_token)}

        self.BASE_URL = endpoint.value
        self._session = requests.Session()
        self._session.headers.update(headers)

    @staticmethod
    def _raise_error(response):
        response_data = response.json()
        if response_data['status'] == 'error':
            raise PuncherClientError(response_data['message'])
        else:
            return response_data

    def _request_get(self, url, **params):
        request_url = urljoin(self.BASE_URL, url)
        req_params = {k: v for k, v in iteritems(params) if v is not None}
        enum_req_params = {k: v.value for k, v in iteritems(req_params) if isinstance(v, Enum)}
        req_params.update(enum_req_params)
        response = self._session.get(request_url, params=req_params)
        return self._raise_error(response)

    def _make_modifying_request(self, action, url, **params):
        request_url = urljoin(self.BASE_URL, url)
        response = self._session.request(action, request_url, json=params)
        return self._raise_error(response)

    def get_requests(self, status=None, source=None, responsible=None, author=None):
        if status is not None:
            status = status.value if isinstance(status, RequestStatus) else ','.join(status)

        response = self._request_get('requests', status=status, source=source, responsible=responsible, author=author)
        self.LOGGER.info('Iterating over first %d of total %d', len(response['requests']), response['count'])
        while response['requests']:
            current_page = response['requests']
            next_page_url = response.get('links', {}).get('next', None)
            for rule_request in current_page:
                yield RuleRequest(**rule_request)

            self.LOGGER.debug('Page eng')
            if next_page_url:
                self.LOGGER.debug('Getting next page')
                response = self._session.get(next_page_url).json()
            else:
                self.LOGGER.debug('All results iterated* Stop iteration')
                break

    def create_request(self, rule_request):
        self.LOGGER.info('Creating rule request %s', rule_request)
        existing_rules = list(self.get_rules(
            source=rule_request.sources,
            destination=rule_request.destinations,
            protocol=rule_request.protocol,
            locations=rule_request.locations,
            #  ports=rule_request.ports  # TODO: fix requests with port ranges
        ))
        if existing_rules:
            self.LOGGER.error('Found existing rules %s', existing_rules)
            raise RuleAlreadyExists('Rule request for already existing rule {}'.format(existing_rules[0]))
        response = self._make_modifying_request('POST', 'requests', request=rule_request.request_dict())
        if response['status'] == 'error':
            raise PuncherClientError(response['message'])
        return RuleRequest(**response['request'])

    def update_request(self, rule_request):
        self.LOGGER.info('Updating rule request %s', rule_request)
        response = self._make_modifying_request('PUT', 'requests/{}'.format(rule_request.id), request=rule_request.request_dict())
        return RuleRequest(**response['request'])

    def reject_request(self, rule_request_id):
        self.LOGGER.info('Rejecting rule request %s', rule_request_id)
        response = self._make_modifying_request('POST', 'requests/{}/reject'.format(rule_request_id))
        return RuleRequest(**response['request'])

    def approve_request(self, rule_request_id):
        self.LOGGER.info('Approving rule request %s', rule_request_id)
        response = self._make_modifying_request('POST', 'requests/{}/approve'.format(rule_request_id))
        return RuleRequest(**response['request'])

    def get_rules(self, source=None, destination=None, protocol=None, locations=None, ports=None, systems=RuleSourceSystem.all):
        response = self._request_get('rules', source=source, destination=destination, protocol=protocol, locations=locations, ports=ports, systems=systems)
        while response['rules']:
            current_page = response['rules']
            next_page_url = response.get('links', {}).get('next', None)
            for rule in current_page:
                yield Rule(**rule)

            if next_page_url:
                response = self._session.get(next_page_url).json()
            else:
                break

    def delete_rule(self, rule_id, comment):
        self.LOGGER.info('Deleting rule %s', rule_id)
        response = self._make_modifying_request('DELETE', 'rules/{}'.format(rule_id), comment=comment)
        return RuleRequest(**response['request'])

    def suggest_sources(self, s):
        return self._request_get('suggest-sources', part=s)

    def suggest_destinations(self, s):
        return self._request_get('suggest-destinations', part=s)
