"""pcoinpay.client

PCOINPay API Client.
"""

import re
import json
from urllib.parse import urlencode

import requests
from requests.exceptions import HTTPError

from . import crypto


class PCOINPayClient:
    def __init__(self, host, pem, insecure=False, tokens=None):
        self.host = host
        self.verify = not(insecure)
        self.pem = pem
        self.tokens = tokens or dict()
        self.client_id = crypto.get_sin_from_pem(pem)
        self.user_agent = 'pcoinpay-python'
        self.s = requests.Session()
        self.s.verify = self.verify
        self.s.headers.update(
            {'Content-Type': 'application/json',
            'accept': 'application/json',
            'X-accept-version': '2.0.0'})

    def _create_signed_headers(self, uri, payload):
        return {
            "X-Identity": crypto.get_compressed_public_key_from_pem(self.pem),
            "X-Signature": crypto.sign(uri + payload, self.pem)
        }

    def _signed_get_request(self, path, params=None, token=None):
        token = token or list(self.tokens.values())[0]
        params = params or dict()
        params['token'] = token

        uri = self.host + path
        payload = '?' + urlencode(params)
        headers = self._create_signed_headers(uri, payload)
        r = self.s.get(uri, params=params, headers=headers)
        r.raise_for_status()
        return r.json()['data']

    def _signed_post_request(self, path, payload, token=None):
        token = token or list(self.tokens.values())[0]
        uri = self.host + path
        payload['token'] = token
        payload = json.dumps(payload)
        headers = self._create_signed_headers(uri, payload)
        r = self.s.post(uri, headers=headers, data=payload)
        if not r.ok:
            if 400 <= r.status_code < 500:
                http_error_msg = u'%s Client Error: \
                        %s for url: %s | body: %s' % (
                            r.status_code,
                            r.reason,
                            r.url,
                            r.text
                        )
            elif 500 <= r.status_code < 600:
                http_error_msg = u'%s Server Error: \
                        %s for url: %s | body: %s' % (
                            r.status_code,
                            r.reason,
                            r.url,
                            r.text
                        )
            if http_error_msg:
                raise HTTPError(http_error_msg, response=r)
        return r.json()['data']

    def _unsigned_request(self, path, payload=None):
        uri = self.host + path
        if payload:
            payload = json.dumps(payload)
            r = self.s.post(uri, data=payload)
        else:
            r = self.s.get(uri)
        r.raise_for_status()
        return r.json()['data']

    def get_rates(self, crypto='BTC', store_id=None):
        params = dict(
            cryptoCode=crypto
        )
        if store_id:
            params['storeID'] = store_id
        return self._signed_get_request('/rates/', params=params)

    def get_rate(self, currency, crypto='BTC', store_id=None):
        rates = self.get_rates(crypto=crypto, store_id=store_id)
        rate = [rate for rate in rates if rate['code'] == currency.upper()][0]
        return rate['rate']

    def create_invoice(self, payload, token=None):
        try:
            float(payload['price'])
        except ValueError as e:
            raise ValueError('Price must be a float') from e
        return self._signed_post_request('/invoices/', payload, token=token)

    def get_invoice(self, invoice_id, token=None):
        return self._signed_get_request('/invoices/' + invoice_id, token=token)

    def get_invoices(self, status=None, order_id=None, item_code=None, date_start=None, date_end=None, limit=None, offset=None, token=None):
        params = dict()
        if status is not None:
            params['status'] = status
        if order_id is not None:
            params['orderId'] = order_id
        if item_code is not None:
            params['itemCode'] = item_code
        if date_start is not None:
            params['dateStart'] = date_start
        if date_end is not None:
            params['dateEnd'] = date_end
        if limit is not None:
            params['limit'] = limit
        if offset is not None:
            params['offset'] = offset
        return self._signed_get_request('/invoices', params=params, token=token)

    def pair_client(self, code):
        if re.match(r'^\w{7,7}$', code) is None:
          raise ValueError("pairing code is not legal")
        payload = {'id': self.client_id, 'pairingCode': code}
        data = self._unsigned_request('/tokens', payload)
        data = data[0]
        return {
            data['facade']: data['token']
        }

    @classmethod
    def create_client(cls, code, host):
        pem = crypto.generate_privkey()
        client = PCOINPayClient(host=host, pem=pem)
        token = client.pair_client(code)
        return PCOINPayClient(host=host, pem=pem, tokens=token)

    @classmethod
    def create_tor_client(cls, code, host, proxy='socks5://127.0.0.1:9050'):
        """ Useful for .onion services, the `proxy` input assumes the default
        proxy header
        """
        pem = crypto.generate_privkey()
        client = PCOINPayClient(host=host, pem=pem)
        client.s.proxies = {
            'http': proxy,
            'https': proxy}
        token = client.pair_client(code)
        final_client = PCOINPayClient(host=host, pem=pem, tokens=token)
        final_client.s.proxies = {
            'http': proxy,
            'https': proxy}
        return final_client


    def __repr__(self):
        return '{}({})'.format(
            type(self).__name__,
            self.host
        )
