import contextlib
import json
import requests

from urllib.parse import urljoin

from .json_encoder import OPJSONEncoder


class OPServiceClient:


  def __init__(self, url=None, auth=None):
    '''
    - url: the base URL of the service to call (trailing '/' is optional)
    - [auth]: an object containing the following properties:
      - route: the name of the route on the target service that we can use to generate a JWT
      - [params]: the params to pass to the 'route' to successfully generate a JWT (i.e. a secret key)

    Example usage (authenticated API):
    url = 'https://op-dev-manager-service.op-dev.io'
    auth = { 'route': 'get-jwt-from-secret', 'params': { secret_id': ..., 'secret': ... } }
    client = OPServiceClient(url, auth)
    teams = client.call('list-teams')
    '''

    self._base_url = url
    self._auth_route = auth and auth.get('route')
    self._auth_params = auth and auth.get('params')
    self._token = None


  def _update_token(self):
    url = urljoin(self._base_url, self._auth_route)
    response = requests.post(url, json=self._auth_params)
    if response.status_code != 200:
      raise Exception('Failed to update auth token', response, response.json())
    self._token = response.json()


  def _call_auth_retry(self, route, data, auth_retry):
    # TODO: we could eagerly optimize this call by checking the token's decoded expires-at time ourselves, instead of
    # calling and waiting for the error response.
    url = urljoin(self._base_url, route)
    headers= { 'Content-Type': 'application/json' }
    if self._token:
      headers['Authorization'] = 'Bearer %s' % self._token
    serialized_data = json.dumps(data, cls=OPJSONEncoder)
    response = requests.post(url, data=serialized_data, headers=headers)
    body = None
    with contextlib.suppress(Exception):
      body = response.json()
    if body and (response.status_code in [ 400, 401 ]):
      if auth_retry and any((
          body['err'] == 'AuthenticationException' and (not self._token),
          body['err'] == 'InvalidAuthorizationHeader' and self._auth_route and (not self._token),
          body['err'] == 'ExpiredToken'
        )):
        self._update_token()
        return self._call_auth_retry(route, data, False)
    if response.status_code != 200:
      raise Exception(response, body)
    return body


  def call(self, route, data=None):
    return self._call_auth_retry(route, data, True)
