#!/usr/bin/python
# -*- coding: utf-8 -*-
from decimal import Decimal
import mock
import warnings

from django.conf import settings
from django.forms import ValidationError
from django.test import TestCase
from django.test.client import RequestFactory

from paypal.pro.fields import CreditCardField
from paypal.pro.helpers import PayPalWPP, PayPalError, VERSION
from paypal.pro.exceptions import PayPalFailure
from paypal.pro.signals import payment_was_successful


RF = RequestFactory()
REQUEST = RF.get("/pay/", REMOTE_ADDR="127.0.0.1:8000")


class DummyPayPalWPP(PayPalWPP):
    pass

#     """Dummy class for testing PayPalWPP."""
#     responses = {
#         # @@@ Need some reals data here.
#         "DoDirectPayment": """ack=Success&timestamp=2009-03-12T23%3A52%3A33Z&l_severitycode0=Error&l_shortmessage0=Security+error&l_longmessage0=Security+header+is+not+valid&version=54.0&build=854529&l_errorcode0=&correlationid=""",
#     }
# 
#     def _request(self, data):
#         return self.responses["DoDirectPayment"]


class CreditCardFieldTest(TestCase):
    def test_CreditCardField(self):
        field = CreditCardField()
        field.clean('4797503429879309')
        self.assertEqual(field.card_type, "Visa")
        self.assertRaises(ValidationError, CreditCardField().clean, '1234567890123455')

    def test_invalidCreditCards(self):
        self.assertEqual(CreditCardField().clean('4797-5034-2987-9309'), '4797503429879309')


class PayPalWPPTest(TestCase):
    def setUp(self):

        self.item = {
            'amt': '9.95',
            'inv': 'inv',
            'custom': 'custom',
            'next': 'http://www.example.com/next/',
            'returnurl': 'http://www.example.com/pay/',
            'cancelurl': 'http://www.example.com/cancel/'
        }
        # Handle different parameters for Express Checkout
        self.ec_item = {
            'paymentrequest_0_amt': '9.95',
            'inv': 'inv',
            'custom': 'custom',
            'next': 'http://www.example.com/next/',
            'returnurl': 'http://www.example.com/pay/',
            'cancelurl': 'http://www.example.com/cancel/'
        }
        self.wpp = DummyPayPalWPP(REQUEST)

    def test_doDirectPayment_missing_params(self):
        data = {'firstname': 'Chewbacca'}
        self.assertRaises(PayPalError, self.wpp.doDirectPayment, data)

    def test_doDirectPayment_valid(self):
        data = {
            'firstname': 'Brave',
            'lastname': 'Star',
            'street': '1 Main St',
            'city': u'San Jos\xe9',
            'state': 'CA',
            'countrycode': 'US',
            'zip': '95131',
            'expdate': '012019',
            'cvv2': '037',
            'acct': '4797503429879309',
            'creditcardtype': 'visa',
            'ipaddress': '10.0.1.199', }
        data.update(self.item)
        self.assertTrue(self.wpp.doDirectPayment(data))

    def test_doDirectPayment_invalid(self):
        data = {
            'firstname': 'Epic',
            'lastname': 'Fail',
            'street': '100 Georgia St',
            'city': 'Vancouver',
            'state': 'BC',
            'countrycode': 'CA',
            'zip': 'V6V 1V1',
            'expdate': '012019',
            'cvv2': '999',
            'acct': '1234567890',
            'creditcardtype': 'visa',
            'ipaddress': '10.0.1.199', }
        data.update(self.item)
        self.assertRaises(PayPalFailure, self.wpp.doDirectPayment, data)

    def test_doDirectPayment_valid_with_signal(self):
        data = {
            'firstname': 'Brave',
            'lastname': 'Star',
            'street': '1 Main St',
            'city': u'San Jos\xe9',
            'state': 'CA',
            'countrycode': 'US',
            'zip': '95131',
            'expdate': '012019',
            'cvv2': '037',
            'acct': '4797503429879309',
            'creditcardtype': 'visa',
            'ipaddress': '10.0.1.199', }
        data.update(self.item)

        self.got_signal = False
        self.signal_obj = None

        def handle_signal(sender, **kwargs):
            self.got_signal = True
            self.signal_obj = sender

        payment_was_successful.connect(handle_signal)
        self.assertTrue(self.wpp.doDirectPayment(data))
        self.assertTrue(self.got_signal)

    def test_setExpressCheckout(self):
        # We'll have to stub out tests for doExpressCheckoutPayment and friends
        # because they're behind paypal's doors.
        nvp_obj = self.wpp.setExpressCheckout(self.ec_item)
        self.assertEqual(nvp_obj.ack, "Success")

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_setExpressCheckout_deprecation(self, mock_request_object):
        mock_request_object.return_value = 'ack=Success&token=EC-XXXX&version=%s'
        item = self.ec_item.copy()
        item.update({'amt': item['paymentrequest_0_amt']})
        del item['paymentrequest_0_amt']
        with warnings.catch_warnings(record=True) as warning_list:
            nvp_obj = self.wpp.setExpressCheckout(item)
            # Make sure our warning was given
            self.assertTrue(any(warned.category == DeprecationWarning
                                for warned in warning_list))
            # Make sure the method still went through
            call_args = mock_request_object.call_args
            self.assertIn('PAYMENTREQUEST_0_AMT=%s' % item['amt'],
                          call_args[0][1])
            self.assertEqual(nvp_obj.ack, "Success")

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_doExpressCheckoutPayment(self, mock_request_object):
        ec_token = 'EC-1234567890'
        payerid = 'LXYZABC1234'
        item = self.ec_item.copy()
        item.update({'token': ec_token, 'payerid': payerid})
        mock_request_object.return_value = 'ack=Success&token=%s&version=%spaymentinfo_0_amt=%s' % \
            (ec_token, VERSION, self.ec_item['paymentrequest_0_amt'])
        wpp = PayPalWPP(REQUEST)
        wpp.doExpressCheckoutPayment(item)
        call_args = mock_request_object.call_args
        self.assertIn('VERSION=%s' % VERSION, call_args[0][1])
        self.assertIn('METHOD=DoExpressCheckoutPayment', call_args[0][1])
        self.assertIn('TOKEN=%s' % ec_token, call_args[0][1])
        self.assertIn('PAYMENTREQUEST_0_AMT=%s' % item['paymentrequest_0_amt'],
                      call_args[0][1])
        self.assertIn('PAYERID=%s' % payerid, call_args[0][1])

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_doExpressCheckoutPayment_invalid(self, mock_request_object):
        ec_token = 'EC-1234567890'
        payerid = 'LXYZABC1234'
        item = self.ec_item.copy()
        item.update({'token': ec_token, 'payerid': payerid})
        mock_request_object.return_value = 'ack=Failure&l_errorcode=42&l_longmessage0=Broken'
        wpp = PayPalWPP(REQUEST)
        with self.assertRaises(PayPalFailure):
            nvp = wpp.doExpressCheckoutPayment(item)

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_doExpressCheckoutPayment_deprecation(self, mock_request_object):
        mock_request_object.return_value = 'ack=Success&token=EC-XXXX&version=%s'
        ec_token = 'EC-1234567890'
        payerid = 'LXYZABC1234'
        item = self.ec_item.copy()
        item.update({'amt': item['paymentrequest_0_amt'],
                     'token': ec_token,
                     'payerid': payerid})
        del item['paymentrequest_0_amt']
        with warnings.catch_warnings(record=True) as warning_list:
            nvp_obj = self.wpp.doExpressCheckoutPayment(item)
            # Make sure our warning was given
            self.assertTrue(any(warned.category == DeprecationWarning
                                for warned in warning_list))
            # Make sure the method still went through
            call_args = mock_request_object.call_args
            self.assertIn('PAYMENTREQUEST_0_AMT=%s' % item['amt'],
                          call_args[0][1])
            self.assertEqual(nvp_obj.ack, "Success")

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_createBillingAgreement(self, mock_request_object):
        mock_request_object.return_value = 'ack=Success&billingagreementid=B-XXXXX&version=%s' % VERSION
        wpp = PayPalWPP(REQUEST)
        nvp = wpp.createBillingAgreement({'token': 'dummy token'})
        call_args = mock_request_object.call_args
        self.assertIn('VERSION=%s' % VERSION, call_args[0][1])
        self.assertIn('METHOD=CreateBillingAgreement', call_args[0][1])
        self.assertIn('TOKEN=dummy+token', call_args[0][1])
        self.assertEqual(nvp.method, 'CreateBillingAgreement')
        self.assertEqual(nvp.ack, 'Success')
        mock_request_object.return_value = 'ack=Failure&l_errorcode=42&l_longmessage0=Broken'
        with self.assertRaises(PayPalFailure):
            nvp = wpp.createBillingAgreement({'token': 'dummy token'})

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_doReferenceTransaction_valid(self, mock_request_object):
        reference_id = 'B-1234'
        amount = Decimal('10.50')
        mock_request_object.return_value = 'ack=Success&paymentstatus=Completed&amt=%s&version=%s&billingagreementid=%s' % \
            (amount, VERSION, reference_id)
        wpp = PayPalWPP(REQUEST)
        nvp = wpp.doReferenceTransaction({'referenceid': reference_id,
                                          'amt': amount})
        call_args = mock_request_object.call_args
        self.assertIn('VERSION=%s' % VERSION, call_args[0][1])
        self.assertIn('METHOD=DoReferenceTransaction', call_args[0][1])
        self.assertIn('REFERENCEID=%s' % reference_id, call_args[0][1])
        self.assertIn('AMT=%s' % amount, call_args[0][1])
        self.assertEqual(nvp.method, 'DoReferenceTransaction')
        self.assertEqual(nvp.ack, 'Success')

    @mock.patch.object(PayPalWPP, '_request', autospec=True)
    def test_doReferenceTransaction_invalid(self, mock_request_object):
        reference_id = 'B-1234'
        amount = Decimal('10.50')
        mock_request_object.return_value = 'ack=Failure&l_errorcode=42&l_longmessage0=Broken'
        wpp = PayPalWPP(REQUEST)
        with self.assertRaises(PayPalFailure):
            nvp = wpp.doReferenceTransaction({'referenceid': reference_id,
                                              'amt': amount})


### DoExpressCheckoutPayment
# PayPal Request:
# {'amt': '10.00',
#  'cancelurl': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname',
#  'custom': u'website_id=480&cname=1',
#  'inv': u'website-480-cname',
#  'method': 'DoExpressCheckoutPayment',
#  'next': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname',
#  'payerid': u'BN5JZ2V7MLEV4',
#  'paymentaction': 'Sale',
#  'returnurl': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname',
#  'token': u'EC-6HW17184NE0084127'}
# 
# PayPal Response:
# {'ack': 'Success',
#  'amt': '10.00',
#  'build': '848077',
#  'correlationid': '375f4773c3d34',
#  'currencycode': 'USD',
#  'feeamt': '0.59',
#  'ordertime': '2009-03-04T20:56:08Z',
#  'paymentstatus': 'Completed',
#  'paymenttype': 'instant',
#  'pendingreason': 'None',
#  'reasoncode': 'None',
#  'taxamt': '0.00',
#  'timestamp': '2009-03-04T20:56:09Z',
#  'token': 'EC-6HW17184NE0084127',
#  'transactionid': '3TG42202A7335864V',
#  'transactiontype': 'expresscheckout',
#  'version': '54.0'}
