""" Sample TVMManger usage
>> tvm_manager = TVMManager()
>> IDENTIFY_URL = 'http://id-test.crypta.yandex.net/identify?yandexuid={yuid}'
>> testing_src, testing_dst = 2002184, 2000649
>> resp = tvm_manager.tvm_get_request(IDENTIFY_URL.format(yuid=u'3962388711498028950'), testing_src, testing_dst)
>> print('Resp is: {}').format(resp.json()[0]['crypta_id'])
"""
import os
import time
import requests
import logging
import ticket_parser2 as tp2
from ticket_parser2.low_level import ServiceContext
from datacloud.dev_utils.logging.logger import get_basic_logger

logger = get_basic_logger(__name__)

__all__ = [
    'TVMTicket',
    'TVMManager',
    'TVM_KEY_REQUEST_URL',
    'TVM_TICKET_REQUEST_URL'
]

TVM_API_URL = 'tvm-api.yandex.net'
TVM_KEY_REQUEST_URL = 'https://{tvm_api_url}/2/keys?lib_version={version}'.format(tvm_api_url=TVM_API_URL, version=tp2.__version__)
TVM_TICKET_REQUEST_URL = 'https://{tvm_api_url}/2/ticket/'.format(tvm_api_url=TVM_API_URL)
DEFAULT_TICKET_TTL = 300  # 5 minutes


def now():
    return int(time.time())


class TVMTicket(object):
    def __init__(self, value, valid_til_timestamp=None):
        """
        args:
            value(str): ticket
            valid_til_timestamp(int, default=None): unix timestamp while ticket is valid. If None, will be set to now + 300seconds
        """
        self.value = value
        if valid_til_timestamp is None:
            self.valid_til_timestamp = now() + DEFAULT_TICKET_TTL
        else:
            self.valid_til_timestamp = valid_til_timestamp

    def __str__(self):
        return self.value

    def is_valid(self, current_timestamp=None):
        """ Return True is ticket is valid at current timestamp and False otherwise
        args:
            current_timestamp(int, default None): unix timestamp in seconds. If not set now() will be used as timestamp
        """
        if current_timestamp is None:
            current_timestamp = now()
        return current_timestamp < self.valid_til_timestamp


class TVMManager(object):
    def __init__(self, url=TVM_API_URL, secret=None, log_level=None):
        """
        args:
            secret(str, default None): TVM Secret should be passed to `secret` variable or saved into TVM_SECRET environment variable.
        """
        self.api_url = TVM_API_URL
        self._api_version = tp2.__version__
        self._ticket_storage = {}
        self._secret = secret or os.getenv('TVM_SECRET', '')
        logger.setLevel(log_level or logging.INFO)
        if not self._secret:
            logger.warn(' TVM secret is empty')

    def get_ticket(self, tvm_src, tvm_dst):
        key = (tvm_src, tvm_dst)
        if key in self._ticket_storage and self._ticket_storage[key].is_valid():
            return self._ticket_storage[key]
        new_ticket = self._request_ticket_with_retry(tvm_src, tvm_dst)
        if new_ticket:
            self._ticket_storage[key] = new_ticket
        return new_ticket

    def tvm_get_request(self, url, tvm_src, tvm_dst, params=None, **kwargs):
        logger.info('Tvm request: {}'.format(url))
        ticket = self.get_ticket(tvm_src, tvm_dst)
        if not ticket:
            raise Exception('Can not make request, no TVM ticket.')

        return requests.get(
            url,
            params=params,
            headers={
                'X-Ya-Service-Ticket': ticket.value
            },
            **kwargs
        )

    def _request_tvm_keys(self):
        return requests.get(TVM_KEY_REQUEST_URL, timeout=0.5).content

    def _get_service_context(self, tvm_src, tvm_keys):
        return ServiceContext(tvm_src, self._secret, tvm_keys)

    def _request_ticket(self, tvm_src, tvm_dst):
        try:
            tvm_keys = self._request_tvm_keys()
            service_context = self._get_service_context(tvm_src, tvm_keys)
            ts = now()
            ticket_response = requests.post(
                TVM_TICKET_REQUEST_URL,
                data={
                    'grant_type': 'client_credentials',
                    'src': tvm_src,
                    'dst': tvm_dst,
                    'ts': ts,
                    'sign': service_context.sign(ts, tvm_dst)
                },
                timeout=0.5
            ).json()
            return TVMTicket(ticket_response[str(tvm_dst)]['ticket'])
        except Exception as ex:
            logger.warn(' Can not request TVM ticket: {}'.format(ex))
        return None

    def _request_ticket_with_retry(self, tvm_src, tvm_dst, n_retry=3):
        for idx in range(n_retry):
            ticket = self._request_ticket(tvm_src, tvm_dst)
            if ticket:
                return ticket
            logger.warn(' TVM ticket request failed, retries left: {}'.format(n_retry - idx))
        logger.warn(' Can not get TVM ticket')
        return None
