import json
from unittest import mock
from unittest.mock import MagicMock, Mock

from django.test import Client, TestCase
from stripe.util import convert_to_stripe_object

from tordonate import container
from tordonate.civicrm.repository import CivicrmRepositoryMock
from tordonate.stripe import StripeController

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 StripeViewTests(TestCase):

    @mock.patch("altcha.verify_solution")
    def test_single_donation(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        client = Client()
        f = open('./tordonate/tests/stripe/json/stripe_create_payment_intent.json')
        mock_payment_intent_return = json.load(f)
        stripe_mock.create_payment_intent.return_value = convert_to_stripe_object(
            mock_payment_intent_return
        )
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': "false",
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertEqual(
                response.content,
                b'{"client_secret": "pi_3PC2fzAPRiS9lYuI2TdctT50"}'
            )
            self.assertFalse(stripe_mock.get_or_create_monthly_plan.called)
            self.assertFalse(stripe_mock.create_customer.called)
            self.assertFalse(stripe_mock.create_subscription.called)

    @mock.patch("altcha.verify_solution")
    def test_single_donation_incorrect_payload(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        client = Client()
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (False, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': "false",
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2Yffff
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhffff
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(
                response.content,
                b'{"error": "ALTCHA payload incorrect"}'
            )

    @mock.patch("altcha.verify_solution")
    def test_single_donation_bad_input(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        stripe_mock.create_payment_intent.return_value = ValueError(
            serviceErrorDisplayMessage
        )
        client = Client()
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': "false",
                    'amount': "one thousand",
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'my email address',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(
                response.content,
                b'{"error": "{\\"amount\\": [{\\"message\\": \\"Enter a whole number.\\", '
                b'\\"code\\": \\"invalid\\"}], \\"email\\": [{\\"message\\": \\"Enter a '
                b'valid email address.\\", \\"code\\": \\"invalid\\"}]}"}'
            )

    @mock.patch("altcha.verify_solution")
    def test_single_donation_sdk_error(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        client = Client()
        stripe_mock.get_or_create_monthly_plan.side_effect = ValueError(
            serviceErrorDisplayMessage
        )
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': True,
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            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."}'
            )

    @mock.patch("altcha.verify_solution")
    def test_single_donation_get_request(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        client = Client()
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response = client.get(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': "false",
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(response.status_code, 405)

    @mock.patch("altcha.verify_solution")
    def test_monthly_donation(self, mock_verify_solution: Mock) -> None:
        client = Client()
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        f = open('./tordonate/tests/stripe/json/stripe_subscription.json')
        mock_sub_return = json.load(f)
        stripe_mock.create_subscription.return_value = convert_to_stripe_object(mock_sub_return)
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': True,
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(response.status_code, 200)
            self.assertTrue(stripe_mock.get_or_create_monthly_plan.called)
            self.assertTrue(stripe_mock.create_customer.called)
            self.assertTrue(stripe_mock.create_subscription.called)
            self.assertEqual(
                response.content,
                b'{"client_secret": '
                b'"pi_3P3J9HAPRiS9lYuI2eaQAITD_secret_006DezIT5s0yQzfErx8rBQVNF"}'
            )

    @mock.patch("altcha.verify_solution")
    def test_monthly_donation_bad_input(self, mock_verify_solution: Mock) -> None:
        client = Client()
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response_bad_input = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': True,
                    'amount': "one thousand",
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'my email address',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(
                response_bad_input.content,
                b'{"error": "{\\"amount\\": [{\\"message\\": \\"Enter a whole number.\\", '
                b'\\"code\\": \\"invalid\\"}], \\"email\\": [{\\"message\\": \\"Enter a '
                b'valid email address.\\", \\"code\\": \\"invalid\\"}]}"}'
            )

    @mock.patch("altcha.verify_solution")
    def test_monthly_donation_incorrect_payload(self, mock_verify_solution: Mock) -> None:
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        client = Client()
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (False, None)
            response = client.post(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': True,
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2Yffff
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhffff
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(
                response.content,
                b'{"error": "ALTCHA payload incorrect"}'
            )

    @mock.patch("altcha.verify_solution")
    def test_monthly_donation_get_request(self, mock_verify_solution: Mock) -> None:
        client = Client()
        civi_mock = MagicMock(spec=CivicrmRepositoryMock)
        stripe_mock = MagicMock(spec=StripeController)
        with (
            container.civi.override(civi_mock),
            container.stripe.override(stripe_mock),
        ):
            mock_verify_solution.return_value = (True, None)
            response_get = client.get(
                '/stripe/process/',
                content_type='application/json',
                follow=True,
                data={
                    'recurring': True,
                    'amount': 1000,
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'email': 'foo@bar.com',
                    'payload': """eyJhbGdvcml0aG0iOiJTSEEtMjU2IiwiY2hhbGxlbmdlIjoiM2UxYzBlZDI2YTc2Y
zE0YzhkNzUwNTI0M2I5NDNjMmIxYjUwYmY3YjcxNzk2Y2YwZGQzNGEzNDkxYmZmZDc4YiIsIm51bWJlciI6MTQyNTQsInNhbHQi
OiJDMGZEV01VcGNTQUM4dz9leHBpcmVzPTE3MTc2MzE1MTciLCJzaWduYXR1cmUiOiI0Yjc0ZWU1YzY3NzU0ZDJkMTFiZjQ5MTR
lM2ZmZWUyMDQ3ZWNjNmNiMDRhYmY0M2I3YzlhMjczNDkwMjA0ZTBhIiwidG9vayI6NzMxMH0=""",
                    'country': 'US',
                    'street_address': '123 Sesame Street',
                    'apartment_number': '2nd floor',  # note: not mandatory
                    'city': 'New York',
                    'state': 'NY',
                    'postal_code': '123456',
                    'cohort': 'B',
                },
            )
            self.assertEqual(response_get.status_code, 405)

    def test_webhook(self) -> None:
        client = Client()
        stripe_mock = MagicMock(spec=StripeController)
        f = open('./tordonate/tests/stripe/json/stripe_webhook.json')
        mock_webhook_data = json.load(f)
        stripe_mock.validate_webhook.return_value = None
        stripe_mock.process_webhook.return_value = None
        stripe_signature = """t=1713992423,v1=
                        de5a85e6d034791b6c1cfe42a262ea8832afaa3d9b9366854e0ea9b9ff667085,
                        v0=ddc270b60e4d0897ec4084a2dd585a91b031f422650b9b60ff301b44a129fadd"""
        header = {
            "connection": "close",
            "accept-encoding": "gzip",
            "stripe-signature": stripe_signature,
            "content-type": "application/json; charset=utf-8",
            "cache-control": "no-cache",
            "accept": "*/*; q=0.5, application/xml",
            "content-length": 2459,
            "user-agent": "Stripe/1.0 (+https://stripe.com/docs/webhooks)",
            "host": "webhook.site",
        }
        with container.stripe.override(stripe_mock):
            response = client.get(
                '/stripe/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_data,
                **header
            )
            self.assertEqual(response.status_code, 200)
            self.assertTrue(stripe_mock.validate_webhook.called)
            self.assertTrue(stripe_mock.process_webhook.called)

    def test_webhook_bad_header(self) -> None:
        client = Client()
        stripe_mock = MagicMock(spec=StripeController)
        f = open('./tordonate/tests/stripe/json/stripe_webhook.json')
        mock_webhook_data = json.load(f)
        stripe_mock.validate_webhook.side_effect = ValueError(
            serviceErrorDisplayMessage
        )
        stripe_signature = "bad_signature_example_123456"
        header = {
            "connection": "close",
            "accept-encoding": "gzip",
            "stripe-signature": stripe_signature,
            "content-type": "application/json; charset=utf-8",
            "cache-control": "no-cache",
            "accept": "*/*; q=0.5, application/xml",
            "content-length": 2459,
            "user-agent": "Stripe/1.0 (+https://stripe.com/docs/webhooks)",
            "host": "webhook.site",
        }
        with container.stripe.override(stripe_mock):
            response = client.get(
                '/stripe/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_data,
                **header
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(stripe_mock.validate_webhook.called)
            self.assertFalse(stripe_mock.process_webhook.called)

    def test_webhook_civi_down(self) -> None:
        client = Client()
        stripe_mock = MagicMock(spec=StripeController)
        f = open('./tordonate/tests/stripe/json/stripe_webhook.json')
        mock_webhook_data = json.load(f)
        stripe_mock.validate_webhook.return_value = None
        stripe_mock.process_webhook.side_effect = ValueError(
            serviceErrorDisplayMessage
        )
        stripe_signature = """t=1713992423,v1=
                        de5a85e6d034791b6c1cfe42a262ea8832afaa3d9b9366854e0ea9b9ff667085,
                        v0=ddc270b60e4d0897ec4084a2dd585a91b031f422650b9b60ff301b44a129fadd"""
        header = {
            "connection": "close",
            "accept-encoding": "gzip",
            "stripe-signature": stripe_signature,
            "content-type": "application/json; charset=utf-8",
            "cache-control": "no-cache",
            "accept": "*/*; q=0.5, application/xml",
            "content-length": 2459,
            "user-agent": "Stripe/1.0 (+https://stripe.com/docs/webhooks)",
            "host": "webhook.site",
        }
        with container.stripe.override(stripe_mock):
            response = client.get(
                '/stripe/webhook/',
                content_type='application/json',
                follow=True,
                data=mock_webhook_data,
                **header
            )
            self.assertEqual(response.status_code, 400)
            self.assertTrue(stripe_mock.validate_webhook.called)
            self.assertTrue(stripe_mock.process_webhook.called)
