import json
from unittest import IsolatedAsyncioTestCase
from unittest.mock import AsyncMock, MagicMock

from django.test import AsyncRequestFactory, TestCase
from fakeredis import FakeRedis

from tordonate.civicrm.repository import CivicrmRepositoryMock
from tordonate.redis.controller import RedisController


class CiviCRMRepositoryTests(TestCase):
    def test_controller_init(self) -> None:
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            FakeRedis(),  # type: ignore
            0,
            False,
            2,
        )
        self.assertEqual(CivicrmRepoTest._redis.set("foo", "bar"), True)  # type: ignore
        self.assertEqual(CivicrmRepoTest._yec_total, 0)
        self.assertEqual(CivicrmRepoTest._is_yec, False)
        self.assertEqual(CivicrmRepoTest._minimum_donation, 2)


class AsyncCiviCRMRepositoryTests(IsolatedAsyncioTestCase):
    form_data_single_donation = {
        "csrfmiddlewaretoken": "ka7vrKac7jPgL2KqUDvJjQGX8jn3yyeconmk9KSo4oDxGfQC5Cc76PzkZufGqTCk",
        "donation_amount": "5",
        "custom_donation": "5",
        "single_or_monthly": "single",
        "perk": "no_gift",
        "first_name": "TestDonorFirst1",
        "last_name": "TestDonorLast1",
        "email": "testdonor1@example.org",
        "email_address": "testdonor1@example.org",
        "email_updates": "on",
        "country": "United States",
        "street_address": "123 Test Ave.",
        "apartment_number": "4",
        "city": "Boston",
        "state": "California",
        "postal_code": "02110",
        "altcha": "ka7vrKac7jPgL2KqUDvJjQGX8jn3yyeconmk9KSo4oDxGfQC5Cc76PzkZufGqTCk=",
        "payload": "ka7vrKac7jPgL2KqUDvJjQGX8jn3yyeconmk9KSo4oDxGfQC5Cc76PzkZufGqTCk=",
        "amount": 500,
        "recurring": False
    }

    def setUp(self) -> None:
        # Every test needs access to the request factory.
        self.factory = AsyncRequestFactory()

    async def test_handle_donation_form_data(self) -> None:
        request = self.factory.post("/process/")
        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.queue_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.queue_donation.side_effect = None

        await CivicrmRepoTest.handle_donation_form_data(
            request,
            self.form_data_single_donation,
            "pi_3PW1uY2XQl7nllXt0v74Z7MX"
        )
        CivicrmRepoTest.queue_donation.assert_called
        CivicrmRepoTest.queue_donation.assert_called_with(
            "pi_3PW1uY2XQl7nllXt0v74Z7MX",
            {
                "donation_amount": "5",
                "custom_donation": "5",
                "perk": "no_gift",
                "first_name": "TestDonorFirst1",
                "last_name": "TestDonorLast1",
                "email": "testdonor1@example.org",
                "email_address": "testdonor1@example.org",
                "email_updates": "on",
                "country": "United States",
                "street_address": "123 Test Ave.",
                "apartment_number": "4",
                "city": "Boston",
                "state": "California",
                "postal_code": "02110",
                "amount": 500,
                "ip": "127.0.0.1"
            }
        )

    async def test_handle_donation_form_data_queue_error(self) -> None:
        request = self.factory.post("/process/")
        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.queue_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.queue_donation.side_effect = ValueError
        with self.assertRaises(ValueError):
            await CivicrmRepoTest.handle_donation_form_data(
                request,
                self.form_data_single_donation,
                "pi_3PW1uY2XQl7nllXt0v74Z7MX"
            )


class AsyncCiviCRMReportDonationTestSuite(IsolatedAsyncioTestCase):
    form_data_single_donation = """{"donation_amount": "12.50", "first_name": "TestDonorFirst1",
"last_name": "TestDonorLast1", "email_updates": "on", "country": "United States",
"street_address": "123 Test", "apartment_number": "ABC", "city": "Topeka",
"state": "Kansas", "postal_code": "12345", "perk_option--dc1dc9a6-ca65-4aef-b223-87d270c0b97f":
"T22-RCF-C03", "email": "testdonor1@example.org", "perk_option": "T22-RCF-C03"}"""

    form_data_recurring_donation = """{"donation_amount": 10200, "first_name": "TestRecurFirst01",
"last_name": "TestRecurLast01", "email_updates": "on", "country": "Canada",
"street_address": "123 rue Bidon", "apartment_number": "ABC", "city": "Montréal",
"state": "Quebec", "postal_code": "H0H 0H0", "email": "testdonorrecur@example.org",
"perk_option": "T22-RCF-C03"}"""

    async def test_report_single_stripe_donation(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "currency": "USD",
            "payment_instrument_id": "Credit Card",
            "receive_date": "2024-05-04 12:47:20",
            "trxn_id": "pi_3PW1uY2XQl7nllXt0v74Z7MX",
            "contribution_status_id": "Completed",
            "handshake_id": "pi_3PW1uY2XQl7nllXt0v74Z7MX",
            "total_amount": "12500",
        }
        resque_data_to_confirm["donation"]: json.loads(  # type: ignore
            self.form_data_single_donation
        )

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = self.form_data_single_donation
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\OneTimeContribution",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\OneTimeContribution:pi_3PW1uY2XQl7nllXt0v74Z7MX",
            "2024-05-04 12:47:20"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "pi_3PW1uY2XQl7nllXt0v74Z7MX"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\OneTimeContribution",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called

    async def test_report_single_paypal_donation(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "payment_instrument_id": "PayPal",
            "receive_date": "2024-05-04 12:47:20",
            "trxn_id": "WH-7RB786821B539884S-2RC08230KG7539033",
            "handshake_id": "WH-7RB786821B539884S-2RC08230KG7539033",
            "currency": "USD",
            "total_amount": "12.50",
            "contribution_status_id": "Completed"
        }
        resque_data_to_confirm["donation"]: json.loads(  # type: ignore
            self.form_data_single_donation
        )

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = self.form_data_single_donation
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\OneTimeContribution",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\OneTimeContribution:WH-7RB786821B539884S-2RC08230KG7539033",
            "2024-05-04 12:47:20"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "WH-7RB786821B539884S-2RC08230KG7539033"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\OneTimeContribution",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\OneTimeContribution",
            "Completed",
            "unknown",
        )

    async def test_report_recurring_stripe_donation(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "currency": "USD",
            "payment_instrument_id": "Credit Card",
            "receive_date": "2024-05-04 12:47:20",
            "trxn_id": "in_1PqIvj2XQl7nllXtpiNPIjGS",
            "contribution_status_id": "Completed",
            "handshake_id": "in_1PqIvj2XQl7nllXtpiNPIjGS",
            "total_amount": "12500",
            "recurring_contribution_transaction_id": "sub_1PqIvj2XQl7nllXt0JYSgVVx",
        }
        resque_data_to_confirm["donation"]: json.loads(  # type: ignore
            self.form_data_recurring_donation
        )

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = self.form_data_recurring_donation
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:in_1PqIvj2XQl7nllXtpiNPIjGS",
            "2024-05-04 12:47:20"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "in_1PqIvj2XQl7nllXtpiNPIjGS"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Completed",
            "unknown",
        )

    async def test_report_recurring_stripe_donation_recurs(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "currency": "USD",
            "payment_instrument_id": "Credit Card",
            "receive_date": "2024-06-04 12:12:48",
            "trxn_id": "in_1PqIxb2XQl7nllXtXfcVlbUP",
            "contribution_status_id": "Completed",
            "handshake_id": "in_1PqIxb2XQl7nllXtXfcVlbUP",
            "total_amount": "12500",
            "recurring_contribution_transaction_id": "sub_1PqIvj2XQl7nllXt0JYSgVVx",
        }

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = None
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:in_1PqIxb2XQl7nllXtXfcVlbUP",
            "2024-06-04 12:12:48"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "in_1PqIxb2XQl7nllXtXfcVlbUP"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Completed",
            "unknown",
        )

    async def test_report_recurring_stripe_donation_expired(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "currency": "USD",
            "payment_instrument_id": "Credit Card",
            "receive_date": "2024-07-04 12:07:58",
            "trxn_id": "in_1PqJ2A2XQl7nllXtPXaP7GGV",
            "contribution_status_id": "Failed",
            "handshake_id": "in_1PqJ2A2XQl7nllXtPXaP7GGV",
            "total_amount": "12500",
            "recurring_contribution_transaction_id": "sub_1PqIvj2XQl7nllXt0JYSgVVx",
        }

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = None
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:in_1PqJ2A2XQl7nllXtPXaP7GGV",
            "2024-07-04 12:07:58"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "in_1PqJ2A2XQl7nllXtPXaP7GGV"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Failed",
            "unknown",
        )

    async def test_report_recurring_paypal_donation(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "payment_instrument_id": "PayPal",
            "receive_date": "2024-05-04 12:47:20",
            "trxn_id": "WH-0MY19510F25751938-8AV99208JY195124J",
            "handshake_id": "I-CTLJS32LT0RP",
            "recurring_contribution_transaction_id": "I-CTLJS32LT0RP",
            "currency": "USD",
            "total_amount": "12.50",
            "contribution_status_id": "Completed"
        }
        resque_data_to_confirm["donation"]: json.loads(  # type: ignore
            self.form_data_recurring_donation
        )

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = self.form_data_recurring_donation
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:WH-0MY19510F25751938-8AV99208JY195124J",
            "2024-05-04 12:47:20"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "I-CTLJS32LT0RP"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Completed",
            "unknown",
        )

    async def test_report_recurring_paypal_donation_recurs(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "payment_instrument_id": "PayPal",
            "receive_date": "2024-06-04 12:33:39",
            "trxn_id": "WH-20J00421WT676543U-5PW45697KD9047313",
            "handshake_id": "I-CTLJS32LT0RP",
            "recurring_contribution_transaction_id": "I-CTLJS32LT0RP",
            "currency": "USD",
            "total_amount": "12.50",
            "contribution_status_id": "Completed"
        }

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = None
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:WH-20J00421WT676543U-5PW45697KD9047313",
            "2024-06-04 12:33:39"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "I-CTLJS32LT0RP"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Completed",
            "unknown",
        )

    async def test_report_recurring_paypal_donation_expired(self) -> None:
        webhook_data = resque_data_to_confirm = {
            "payment_instrument_id": "PayPal",
            "receive_date": "2024-07-04 12:19:04",
            "trxn_id": "WH-7GC60456AE316900B-9U605776VL670404H",
            "handshake_id": "I-CTLJS32LT0RP",
            "recurring_contribution_transaction_id": "I-CTLJS32LT0RP",
            "currency": "USD",
            "total_amount": "12.50",
            "contribution_status_id": "Failed"
        }

        MockRedis = MagicMock(spec=RedisController)
        CivicrmRepoTest = CivicrmRepositoryMock(  # type: ignore
            MockRedis,
            0,
            False,
            2,
        )
        CivicrmRepoTest.note_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.note_donation.side_effect = None
        CivicrmRepoTest.retrieve_donation = AsyncMock()  # type: ignore
        CivicrmRepoTest.retrieve_donation.return_value = None
        CivicrmRepoTest.donation_transaction_counter = AsyncMock()  # type: ignore
        await CivicrmRepoTest.report_donation(
            "Tor\\Donation\\RecurringContributionOngoing",
            webhook_data
        )

        CivicrmRepoTest.note_donation.assert_called
        CivicrmRepoTest.note_donation.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing:WH-7GC60456AE316900B-9U605776VL670404H",
            "2024-07-04 12:19:04"
        )
        CivicrmRepoTest.retrieve_donation.assert_called
        CivicrmRepoTest.retrieve_donation.assert_called_with(
            "I-CTLJS32LT0RP"
        )
        MockRedis.resque_send.assert_called
        MockRedis.resque_send.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            resque_data_to_confirm
        )
        CivicrmRepoTest.donation_transaction_counter.assert_called
        CivicrmRepoTest.donation_transaction_counter.assert_called_with(
            "Tor\\Donation\\RecurringContributionOngoing",
            "Failed",
            "unknown",
        )
