import sys
import gnupg
import json
import requests
import responses
import datetime

from pprint import pprint

class CryptoTools():
    def __init__(self, homedir=None, verify=False, sign=True,
                 my_fingerprint=None, passphrase=None, recv_keys=False,
                 keyserver='hkp://pgp.mit.edu'):
        self.homedir = homedir
        self.verify = verify
        self.sign = sign
        self._gpg = gnupg.GPG(homedir=self.homedir)
        self._gpg.keyserver = keyserver
        self.passphrase = passphrase
        self.public_keys = self._gpg.list_keys()
        self.recv_keys = recv_keys
        self.my_fingerprint = my_fingerprint

    def decrypt(self, msg=None):
        if msg is None:
            raise(ValueError)
        ret = self._gpg.decrypt(msg, always_trust=True,
                                passphrase=self.passphrase)

        odata = {}
        odata['signature_fingerprint'] = ret.fingerprint
        odata['signature_pubkey_fingerprint'] = ret.pubkey_fingerprint
        odata['signature_keyid'] = ret.key_id
        odata['creation_date'] = ret.creation_date
        odata['timestamp'] = ret.timestamp
        odata['sig_timestamp'] = ret.sig_timestamp
        odata['username'] = ret.username
        odata['expire_timestamp'] = ret.expire_timestamp
        odata['trust_level'] = ret.trust_level
        odata['trust_text'] = ret.trust_text
        odata['notations'] = ret.notations

        return({'decrypted_response': str(ret),
                'openpgp_data': odata})

    def encrypt(self, msg=None, recipient=None):
        if msg is None:
            raise(ValueError)
        if recipient is None:
            raise(ValueError)
        if self.validate_recipient(recipient) is False:
            raise(ValueError)
        kwargs = {}
        if self.my_fingerprint is not None and self.passphrase is not None:
            kwargs['default_key'] = self.my_fingerprint
            kwargs['passphrase'] = self.passphrase  # TODO: securify
        e = self._gpg.encrypt(msg, recipient, **kwargs)
        return(e)

    def validate_recipient(self, recipient):
        pks = self._gpg.list_keys()
        found = False
        for key in pks:
            if key['fingerprint'] == recipient:
                found = True
                break
        if self.recv_keys is True and found is False:
            print("Getting key {} from {}".format(recipient,
                                                  self._gpg.keyserver))
            x = self._gpg.recv_keys(recipient)
            if recipient in x.fingerprints:
                found = True
        return(found)


class OpenPGPApiRequest():
    CONTENT_TYPE = 'application/openpgp-api'
    USER_AGENT = 'python-openpgp-api-request/0.1'

    def __init__(self, server='localhost', port=5080, endpoint='/',
                 recipient=None, sign=False, homedir=None,
                 verify=False, ssl=False, auto_recv_keys=True,
                 my_fingerprint=None, passphrase=None):
        # FIX/RFC: should I self.session = requests.session() or something
        # like it?  maybe it's own method...
        self.method = 'POST'
        if ssl:
            self.protocol = 'https'
            self.verify = verify
        else:
            self.protocol = 'http'
            self.verify = None
        self.server = server
        self.port = port
        if endpoint.startswith('/'):
            self.endpoint = endpoint
        else:
            self.endpoint = '/{}'.format(endpoint)
        self.target = '{}://{}:{}{}'.format(self.protocol,
                                            self.server,
                                            self.port,
                                            self.endpoint)

        if recipient is None:
            self.get_server_identity()
        else:
            self.recipient = recipient
        self.sign = sign

        self.crypto = CryptoTools(homedir=homedir,
                                  recv_keys=auto_recv_keys,
                                  my_fingerprint=my_fingerprint,
                                  passphrase=passphrase)

    def get_server_identity(self):
        cr = self._clear_request(cmd='identity')
        if 'identity' in cr:
            # TODO: VALIDATE
            self.recipient = cr['identity']
        else:
            self.recipient = None

    def get_server_trust(self):
        cr = self._signed_request(cmd='trust')
        pprint(cr)

    def head(self, endpoint=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='GET',
                                 endpoint=endpoint,
                                 params=params, **kwargs)
        return(r)
        # TODO: extract path, params, then pgpapi-ize and send

    def get(self, endpoint=None, params=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='GET',
                                 endpoint=endpoint,
                                 params=params, **kwargs)
        return(r)

    def post(self, endpoint=None, data=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='POST',
                                 endpoint=endpoint,
                                 data=data, **kwargs)
        return(r)

    def put(self, endpoint=None, data=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='PUT', endpoint=endpoint, data=data, **kwargs)
        return(r)

    def patch(self, endpoint=None, data=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='PATCH', endpoint=endpoint, data=data, **kwargs)
        return(r)

    def delete(self, endpoint=None, **kwargs):
        if endpoint is None:
            return(None)
        r = self._pgpapi_request(method='DELETE', endpoint=endpoint, **kwargs)
        return(r)

    def _clear_request(self, cmd=None):
        if cmd is None:
            raise(ValueError)
        url = '{}/clear'.format(self.target)
        r = requests.get(url, params={'cmd': cmd})
        try:
            j = r.json()
        except Exception:
            j = []
        return(j)

    def _pgpapi_request(self, method=None, endpoint=None,
                        params=None, data=None, **kwargs):

        if self.recipient is None:
            raise(ValueError)  # TODO: create custom exception

        # encrypted_data must include all things that should not be visible:
        # headers, data/params, real api url, method, etc

        # add content-type: application/openpgp-api
        # Now we need to build encrypted_data. We are encapsulating, depending
        # on method: endpoint, params/data, headers, etc.
        capsule = {}
        try:
            isonow = datetime.datetime.utcnow().isoformat()
            capsule['request_utc_datetime'] = isonow
            capsule['method'] = method
            capsule['url'] = endpoint
            capsule['params'] = json.dumps(params)
            capsule['data'] = json.dumps(data)
            capsule['headers'] = kwargs['headers']
            del(kwargs['headers'])
        except Exception:
            pass
        json_capsule = json.dumps(capsule)
        encrypted_data = str(self.crypto.encrypt(msg=json_capsule,
                                                 recipient=self.recipient))
        local_headers = {'Content-Type': OpenPGPApiRequest.CONTENT_TYPE,
                         'User-Agent': OpenPGPApiRequest.USER_AGENT,
                         'Accept': self.CONTENT_TYPE, }
        r = requests.request(method=self.method, url=self.target,
                             data=encrypted_data,
                             stream=False,
                             headers=local_headers,
                             verify=self.verify, **kwargs)
        dr = self.crypto.decrypt(r.content)
        decrypted = json.loads(dr['decrypted_response'])
        odata = dr['openpgp_data']
        body = json.loads(decrypted['content'])
        status = decrypted['status']
        headers = json.loads(decrypted['headers'])
        HEADER = 'X-OpenPGP-Data-JSON-string'
        headers[HEADER] = json.dumps(odata)
        resp = responses.Response(method=method,
                                  url=endpoint,
                                  body=body,
                                  status=status,
                                  content_type=headers['Content-Type'],
                                  headers=headers)
        resp.body = resp.body.decode('utf-8')
        return(resp)
        # TODO: add self.sign check to encrypted_data

# TODO: add headers, cookies, auth, timeout, proxies, verify
# http://docs.python-requests.org/en/master/api/

# Encryption: https://pythonhosted.org/gnupg/gnupg.html


def test_openpgp_api_server():
    # This is a test method, hardcoded and horrible
    fpr = 'AFINGERPRINTHERE'
    h = '/path/to/keyring/folder/containing/that/fpr'
    x = OpenPGPApiRequest(homedir=h,
                          my_fingerprint=fpr,
                          passphrase='the_passphrase')
    z = x.get(endpoint='get', headers={'X-extra-header': 'wow'})
    print(z.body)
    z = x.get(endpoint='tuvieja',
              headers={'X-header': 'X-value-get'})
    print(z.body)
    z = x.post(endpoint='tuotravieja',
               headers={'X-header': 'X-value-post'},
               data={'data': 'dataaaaa en post'})
    print(z.body)
    z = x.put(endpoint='cuac',
              headers={'X-header': 'X-value-put'},
              data={'data': 'dataaaaa en put'})
    print(z.body)


if __name__ == '__main__':
    test_openpgp_api_server()
