import json
from unittest.mock import MagicMock

from django.test import Client, TestCase

from tordonate import container
from tordonate.civicrm.repository import CivicrmRepositoryMock
from tordonate.paypal import PaypalController

serviceErrorDisplayMessage = """An error occurred while processing your card.
Please wait and try your donation again later.
If this error persists, please email admin@torproject.net."""


class PaypalViewTests(TestCase):
    def test_create_order(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.create_order.return_value = {"id": "2K425487JK810115A"}
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        civi_mock.handle_donation_form_data.return_value = None
        client = Client()
        with (
            container.civi.override(civi_mock),
            container.paypal.override(paypal_mock),
        ):
            response = client.post(
                '/paypal/create-order/',
                content_type='application/json',
                follow=True,
                data={
                    'payment_amount': '10.00',
                    'form': '{}'
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.content, b'{"id": "2K425487JK810115A"}')
            self.assertTrue(paypal_mock.create_order.called)

    def test_create_order_bad_input(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.create_order.side_effect = ValueError(serviceErrorDisplayMessage)
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/create-order/',
                content_type='application/json',
                follow=True,
                data={
                    'payment_amount': '1000',
                    'form': '{}'
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"error": "An error occurred while processing your card.\\n'
                b'Please wait and try your donation again later.\\n'
                b'If this error persists, please email admin@torproject.net."}'
            )
            self.assertTrue(paypal_mock.create_order.called)

    def test_create_order_get_request(self) -> None:
        client = Client()
        response = client.get(
            '/paypal/create-order/',
            content_type='application/json',
            follow=True,
            data={
                'payment_amount': '1000',
                'form': '{}'
            },
        )
        self.assertEqual(response.status_code, 405)

    def test_capture_order_payment(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        client = Client()
        with open('./tordonate/tests/paypal/data/paypal_capture_payment.json') as f:
            mock_capture_payment_return = json.load(f)
        paypal_mock.capture_payment.return_value = mock_capture_payment_return
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/capture-order-payment/',
                content_type='application/json',
                follow=True,
                data={
                    'order_id': '8E608382JG967500B',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                str(response.content, "UTF-8"),
                json.dumps(mock_capture_payment_return)
            )
            self.assertTrue(paypal_mock.capture_payment.called)

    def test_capture_order_payment_bad_input(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.capture_payment.side_effect = ValueError(serviceErrorDisplayMessage)
        client = Client()
        # Testing against "right datatype, bad input"
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/capture-order-payment/',
                content_type='application/json',
                follow=True,
                data={
                    'order_id': 'bad_order_id',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"error": "An error occurred while processing your card.\\n'
                b'Please wait and try your donation again later.\\n'
                b'If this error persists, please email admin@torproject.net."}'
            )
            self.assertTrue(paypal_mock.capture_payment.called)
        # Testing against "wrong datatype, bad input"
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/capture-order-payment/',
                content_type='application/json',
                follow=True,
                data={
                    'order_id': 12345,
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"error": "An error occurred while processing your card.\\n'
                b'Please wait and try your donation again later.\\n'
                b'If this error persists, please email admin@torproject.net."}'
            )
            self.assertTrue(paypal_mock.capture_payment.called)

    def test_capture_order_payment_get_request(self) -> None:
        client = Client()
        response = client.get(
            '/paypal/capture-order-payment/',
            content_type='application/json',
            follow=True,
            data={
                'order_id': '8E608382JG967500B',
            },
        )
        self.assertEqual(response.status_code, 405)

    def test_create_subscription(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        client = Client()
        paypal_mock.get_or_create_product.return_value = {"id": "b9e71bcd19e545c09e2489c105981ed2"}
        paypal_mock.create_subscription.return_value = {"id": "P-4YS42308B22382802MY4BSQA"}
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/create-subscription/',
                content_type='application/json',
                follow=True,
                data={
                    'payment_amount': '5.00',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"id": "P-4YS42308B22382802MY4BSQA"}'
            )
            self.assertTrue(paypal_mock.get_or_create_product.called)
            self.assertTrue(paypal_mock.create_subscription.called)

    def test_create_subscription_bad_input(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        client = Client()
        paypal_mock.get_or_create_product.return_value = {"id": "b9e71bcd19e545c09e2489c105981ed2"}
        paypal_mock.create_subscription.side_effect = ValueError(serviceErrorDisplayMessage)
        # Testing against "right datatype, bad input"
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/create-subscription/',
                content_type='application/json',
                follow=True,
                data={
                    'payment_amount': 'five dollars',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"error": "An error occurred while processing your card.\\n'
                b'Please wait and try your donation again later.\\n'
                b'If this error persists, please email admin@torproject.net."}'
            )
            self.assertTrue(paypal_mock.get_or_create_product.called)
            self.assertTrue(paypal_mock.create_subscription.called)

    def test_create_subscription_get_request(self) -> None:
        client = Client()
        response = client.get(
            '/paypal/create-subscription/',
            content_type='application/json',
            follow=True,
            data={
                'payment_amount': '5.00',
            },
        )
        self.assertEqual(response.status_code, 405)

    def test_ipn(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_ipn.return_value = True
        with open('./tordonate/tests/paypal/data/paypal_ipn_successful.txt') as f:
            mock_ipn_input = f.read()
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal-ipn',
                follow=True,
                text=mock_ipn_input,
            )
            self.assertEqual(response.status_code, 200)
            self.assertTrue(paypal_mock.validate_ipn.called)
            self.assertTrue(paypal_mock.process_ipn.called)

    def test_ipn_message_malformed(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_ipn.return_value = False
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal-ipn',
                follow=True,
                text="bad IPN message",
            )
            self.assertEqual(response.status_code, 200)
            self.assertTrue(paypal_mock.validate_ipn.called)
            self.assertFalse(paypal_mock.process_ipn.called)

    def test_ipn_validation_service_failed(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_ipn.side_effect = ValueError(serviceErrorDisplayMessage)
        with open('./tordonate/tests/paypal/data/paypal_ipn_successful.txt') as f:
            mock_ipn_input = f.read()
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal-ipn',
                follow=True,
                text=mock_ipn_input,
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(paypal_mock.validate_ipn.called)
            self.assertFalse(paypal_mock.process_ipn.called)

    def test_ipn_civi_down(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_ipn.return_value = True
        paypal_mock.process_ipn.side_effect = ValueError(serviceErrorDisplayMessage)
        with open('./tordonate/tests/paypal/data/paypal_ipn_successful.txt') as f:
            mock_ipn_input = f.read()
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal-ipn',
                follow=True,
                text=mock_ipn_input,
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(paypal_mock.validate_ipn.called)
            self.assertTrue(paypal_mock.process_ipn.called)

    def test_ipn_get_request(self) -> None:
        client = Client()
        with open('./tordonate/tests/paypal/data/paypal_ipn_successful.txt') as f:
            mock_ipn_input = f.read()
        response = client.get(
            '/paypal-ipn',
            follow=True,
            text=mock_ipn_input
        )
        self.assertEqual(response.status_code, 405)

    def test_webhook(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_webhook.return_value = {"verification_status": "SUCCESS"}
        with open('./tordonate/tests/paypal/data/paypal_webhook.json') as f:
            mock_webhook_input = json.load(f)
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_input,
            )
            self.assertEqual(response.status_code, 200)
            self.assertTrue(paypal_mock.validate_webhook.called)
            self.assertTrue(paypal_mock.process_webhook.called)

    def test_webhook_bad_input(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/webhook/',
                content_type='application/json',
                follow=True,
                data="Non-JSON input",
            )
            self.assertEqual(response.status_code, 400)
            self.assertFalse(paypal_mock.validate_webhook.called)
            self.assertFalse(paypal_mock.process_webhook.called)

    def test_webhook_verification_failed(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_webhook.side_effect = ValueError(serviceErrorDisplayMessage)
        with open('./tordonate/tests/paypal/data/paypal_webhook.json') as f:
            mock_webhook_input = json.load(f)
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_input,
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(paypal_mock.validate_webhook.called)
            self.assertFalse(paypal_mock.process_webhook.called)

    def test_webhook_civi_down(self) -> None:
        paypal_mock = MagicMock(spec=PaypalController)
        paypal_mock.validate_webhook.return_value = {"verification_status": "SUCCESS"}
        paypal_mock.process_webhook.side_effect = ValueError(serviceErrorDisplayMessage)
        with open('./tordonate/tests/paypal/data/paypal_webhook.json') as f:
            mock_webhook_input = json.load(f)
        client = Client()
        with container.paypal.override(paypal_mock):
            response = client.post(
                '/paypal/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_input,
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(paypal_mock.validate_webhook.called)
            self.assertTrue(paypal_mock.process_webhook.called)

    def test_webhook_get_request(self) -> None:
        client = Client()
        response = client.get(
            '/paypal/webhook/',
            content_type='application/json',
            follow=True,
            data={
                'payment_amount': '10.00',
            },
        )
        self.assertEqual(response.status_code, 405)
