from .exceptions import InvalidPaypalData, RequestError
from six.moves import urllib
import requests
import six


class PaypalPayments(object):
    """
    Base Paypal payments class. Defines endpoints and allows a processed
    Paypal response to be conferted into a PaypalResponse object
    """

    live_endpoint = 'https://www.paypal.com/cgi-bin/webscr'
    sandbox_endpoint = 'https://www.sandbox.paypal.com/cgi-bin/webscr'

    sandbox = False
    endpoint = ''
    response = None
    confirmed = False
    details = None

    def send_confirmation(self):
        raise NotImplementedError()

    def process_response(self):
        raise NotImplementedError()

    def to_paypal_response(self):
        return PaypalResponse(self.confirmed, self.details)


class PaypalResponse(object):
    """
    Wrapper around Paypal response data for more consistent api
    """

    def __init__(self, confirmed, details):
        self.confirmed = confirmed
        self.details = details


class PaypalPDT(PaypalPayments):
    """
    For a given TransactionID confirm a payment via Paypal PDT and
    process the response
    """

    def __init__(self, transaction_id, identity_token, sandbox=False, endpoint=''):
        if not transaction_id or not isinstance(transaction_id, six.string_types):
            raise InvalidPaypalData('{0} is not a valid Paypal TransactionID'.format(transaction_id))
        if not identity_token or not isinstance(identity_token, six.string_types):
            raise InvalidPaypalData('{0} is not a valid Paypal identity token'.format(identity_token))
        self.transaction_id = transaction_id
        self.identity_token = identity_token
        self.sandbox = sandbox
        if endpoint:
            self.endpoint = endpoint
        else:
            self.endpoint = self.live_endpoint if not self.sandbox else self.sandbox_endpoint

    def send_confirmation(self):
        params = {'cmd': '_notify-synch', 'tx': self.transaction_id, 'at': self.identity_token}
        try:
            self.response = requests.post(self.endpoint, params=params)
        except requests.exceptions.RequestException as e:
            raise RequestError(e.args[0])
        if self.response.status_code != 200:
            raise RequestError('Paypal returned a {0} status'.format(self.response.status_code))

    def process_response(self):
        if self.response:
            data = self.response.text.strip().split('\n')
            self.confirmed = True if data[0].strip() == 'SUCCESS' else False
            self.details = {}
            for field in data[1:]:
                field = field.split('=', 1)
                if len(field) != 2:
                    continue
                name = field[0]
                value = field[1]
                self.details[name] = urllib.parse.unquote_plus(value.strip())


class PaypalIPN(PaypalPayments):
    """
    For a given set of query params confirm a payment via Paypal IPN and
    process the response
    """

    def __init__(self, query_params, sandbox=False, endpoint=''):
        if isinstance(query_params, six.string_types):
            self.query_params = query_params
        elif isinstance(query_params, dict):
            self.query_params = urllib.parse.urlencode(query_params)
        else:
            raise InvalidPaypalData('{0} is not a valid IPN query string'.format(query_params))
        self.sandbox = sandbox
        if endpoint:
            self.endpoint = endpoint
        else:
            self.endpoint = self.live_endpoint if not self.sandbox else self.sandbox_endpoint

    def send_confirmation(self):
        headers = {'content_type': 'x-www-form-urlencoded'}
        post_url = '{0}?{1}&{2}'.format(self.endpoint, 'cmd=_notify-validate', self.query_params)
        try:
            self.response = requests.post(post_url, headers=headers)
        except requests.exceptions.RequestException as e:
            raise RequestError(e.args[0])
        if self.response.status_code != 200:
            raise RequestError('Paypal returned a {0} status'.format(self.response.status_code))

    def process_response(self):
        if self.response:
            if self.response.text.strip() == 'VERIFIED':
                self.confirmed = True
            else:
                self.confirmed = False
        self.details = dict((k, v[0].strip()) for (k, v) in urllib.parse.parse_qs(self.query_params, keep_blank_values=True).items()) # no dict comprehensions in Python 2.6 :(
