# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.urls import reverse
from member.models import Club, Membership
from note.models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
    MembershipTransaction, SpecialTransaction, NoteSpecial, Alias
from permission.models import Role


class TestTransactions(TestCase):
    fixtures = ('initial', )

    def setUp(self) -> None:
        self.user = User.objects.create_superuser(
            username="toto",
            password="totototo",
            email="toto@example.com",
        )

        sess = self.client.session
        sess["permission_mask"] = 42
        sess.save()
        self.client.force_login(self.user)

        membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
        membership.roles.add(Role.objects.get(name="Respo info"))
        membership.save()
        Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
        self.user.note.refresh_from_db()

        self.second_user = User.objects.create(
            username="toto2",
        )
        # Non superusers have no note until the registration get validated
        NoteUser.objects.create(user=self.second_user)

        self.club = Club.objects.create(
            name="clubtoto",
        )

        self.transaction = Transaction.objects.create(
            source=self.second_user.note,
            destination=self.user.note,
            amount=4200,
            reason="Test transaction",
        )
        self.user.note.refresh_from_db()
        self.second_user.note.refresh_from_db()

        self.category = TemplateCategory.objects.create(name="Test")
        self.template = TransactionTemplate.objects.create(
            name="Test",
            destination=self.club.note,
            category=self.category,
            amount=100,
            description="Test template",
        )

    def test_admin_pages(self):
        """
        Load some admin pages to check that they render successfully.
        """
        response = self.client.get(reverse("admin:index") + "note/note/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/" + str(self.transaction.pk) + "/change/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
                                   + str(ContentType.objects.get_for_model(Transaction).id))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
                                   + str(ContentType.objects.get_for_model(RecurrentTransaction).id))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
                                   + str(ContentType.objects.get_for_model(MembershipTransaction).id))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
                                   + str(ContentType.objects.get_for_model(SpecialTransaction).id))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/transactiontemplate/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "note/templatecategory/")
        self.assertEqual(response.status_code, 200)

    def test_render_transfer_page(self):
        response = self.client.get(reverse("note:transfer"))
        self.assertEqual(response.status_code, 200)

    def test_transfer_api(self):
        old_user_balance = self.user.note.balance
        old_second_user_balance = self.second_user.note.balance
        quantity = 3
        amount = 314
        total = quantity * amount
        response = self.client.post("/api/note/transaction/transaction/", data=dict(
            quantity=quantity,
            amount=amount,
            reason="Transaction through API",
            valid=True,
            polymorphic_ctype=ContentType.objects.get_for_model(Transaction).id,
            resourcetype="Transaction",
            source=self.user.note.id,
            source_alias=self.user.username,
            destination=self.second_user.note.id,
            destination_alias=self.second_user.username,
        ))
        self.assertEqual(response.status_code, 201)  # 201 = Created
        self.assertTrue(Transaction.objects.filter(reason="Transaction through API").exists())

        self.user.note.refresh_from_db()
        self.second_user.note.refresh_from_db()

        self.assertTrue(self.user.note.balance == old_user_balance - total)
        self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)

        self.test_render_transfer_page()

    def test_credit_api(self):
        old_user_balance = self.user.note.balance
        amount = 4242
        special_type = NoteSpecial.objects.first()
        response = self.client.post("/api/note/transaction/transaction/", data=dict(
            quantity=1,
            amount=amount,
            reason="Credit through API",
            valid=True,
            polymorphic_ctype=ContentType.objects.get_for_model(SpecialTransaction).id,
            resourcetype="SpecialTransaction",
            source=special_type.id,
            source_alias=str(special_type),
            destination=self.user.note.id,
            destination_alias=self.user.username,
            last_name="TOTO",
            first_name="Toto",
        ))

        self.assertEqual(response.status_code, 201)  # 201 = Created
        self.assertTrue(Transaction.objects.filter(reason="Credit through API").exists())
        self.user.note.refresh_from_db()
        self.assertTrue(self.user.note.balance == old_user_balance + amount)

        self.test_render_transfer_page()

    def test_debit_api(self):
        old_user_balance = self.user.note.balance
        amount = 4242
        special_type = NoteSpecial.objects.first()
        response = self.client.post("/api/note/transaction/transaction/", data=dict(
            quantity=1,
            amount=amount,
            reason="Debit through API",
            valid=True,
            polymorphic_ctype=ContentType.objects.get_for_model(SpecialTransaction).id,
            resourcetype="SpecialTransaction",
            source=self.user.note.id,
            source_alias=self.user.username,
            destination=special_type.id,
            destination_alias=str(special_type),
            last_name="TOTO",
            first_name="Toto",
        ))
        self.assertEqual(response.status_code, 201)  # 201 = Created
        self.assertTrue(Transaction.objects.filter(reason="Debit through API").exists())
        self.user.note.refresh_from_db()
        self.assertTrue(self.user.note.balance == old_user_balance - amount)

        self.test_render_transfer_page()

    def test_render_consos_page(self):
        response = self.client.get(reverse("note:consos"))
        self.assertEqual(response.status_code, 200)

    def test_consumption_api(self):
        old_user_balance = self.user.note.balance
        old_club_balance = self.club.note.balance
        quantity = 2
        template = self.template
        total = quantity * template.amount
        response = self.client.post("/api/note/transaction/transaction/", data=dict(
            quantity=quantity,
            amount=template.amount,
            reason="Consumption through API (" + template.name + ")",
            valid=True,
            polymorphic_ctype=ContentType.objects.get_for_model(RecurrentTransaction).id,
            resourcetype="RecurrentTransaction",
            source=self.user.note.id,
            source_alias=self.user.username,
            destination=self.club.note.id,
            destination_alias=self.second_user.username,
            template=template.id,
        ))
        self.assertEqual(response.status_code, 201)  # 201 = Created
        self.assertTrue(Transaction.objects.filter(destination=self.club.note).exists())

        self.user.note.refresh_from_db()
        self.club.note.refresh_from_db()

        self.assertTrue(self.user.note.balance == old_user_balance - total)
        self.assertTrue(self.club.note.balance == old_club_balance + total)

        self.test_render_consos_page()

    def test_invalidate_transaction(self):
        old_second_user_balance = self.second_user.note.balance
        old_user_balance = self.user.note.balance
        total = self.transaction.total
        response = self.client.patch("/api/note/transaction/transaction/" + str(self.transaction.pk) + "/", data=dict(
            valid=False,
            resourcetype="Transaction",
            invalidity_reason="Test invalidate",
        ), content_type="application/json")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(Transaction.objects.filter(valid=False, invalidity_reason="Test invalidate").exists())

        self.second_user.note.refresh_from_db()
        self.user.note.refresh_from_db()

        self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)
        self.assertTrue(self.user.note.balance == old_user_balance - total)

        self.test_render_transfer_page()
        self.test_render_consos_page()

        # Now we check if we can revalidate
        old_second_user_balance = self.second_user.note.balance
        old_user_balance = self.user.note.balance
        total = self.transaction.total
        response = self.client.patch("/api/note/transaction/transaction/" + str(self.transaction.pk) + "/", data=dict(
            valid=True,
            resourcetype="Transaction",
        ), content_type="application/json")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(Transaction.objects.filter(valid=True, pk=self.transaction.pk).exists())

        self.second_user.note.refresh_from_db()
        self.user.note.refresh_from_db()

        self.assertTrue(self.second_user.note.balance == old_second_user_balance - total)
        self.assertTrue(self.user.note.balance == old_user_balance + total)

        self.test_render_transfer_page()
        self.test_render_consos_page()

    def test_render_template_list(self):
        response = self.client.get(reverse("note:template_list") + "?search=test")
        self.assertEqual(response.status_code, 200)

    def test_render_template_create(self):
        response = self.client.get(reverse("note:template_create"))
        self.assertEqual(response.status_code, 200)
        response = self.client.post(reverse("note:template_create"), data=dict(
            name="Test create button",
            destination=self.club.note.pk,
            category=self.category.pk,
            amount=4200,
            description="We have created a button",
            highlighted=True,
            display=True,
        ))
        self.assertRedirects(response, reverse("note:template_list"), 302, 200)
        self.assertTrue(TransactionTemplate.objects.filter(name="Test create button").exists())

    def test_render_template_update(self):
        response = self.client.get(self.template.get_absolute_url())
        self.assertEqual(response.status_code, 200)
        response = self.client.post(self.template.get_absolute_url(), data=dict(
            name="Test update button",
            destination=self.club.note.pk,
            category=self.category.pk,
            amount=4200,
            description="We have updated a button",
            highlighted=True,
            display=True,
        ))
        self.assertRedirects(response, reverse("note:template_list"), 302, 200)
        self.assertTrue(TransactionTemplate.objects.filter(name="Test update button", pk=self.template.pk).exists())

        # Check that the price history renders properly
        response = self.client.post(self.template.get_absolute_url(), data=dict(
            name="Test price history",
            destination=self.club.note.pk,
            category=self.category.pk,
            amount=4200,
            description="We have updated a button",
            highlighted=True,
            display=True,
        ))
        self.assertRedirects(response, reverse("note:template_list"), 302, 200)
        self.assertTrue(TransactionTemplate.objects.filter(name="Test price history", pk=self.template.pk).exists())
        response = self.client.get(reverse("note:template_update", args=(self.template.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_render_search_transactions(self):
        response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
            source=self.second_user.note.alias_set.first().id,
            destination=self.user.note.alias_set.first().id,
            type=[ContentType.objects.get_for_model(Transaction).id],
            reason="test",
            valid=True,
            amount_gte=0,
            amount_lte=42424242,
            created_after="2000-01-01 00:00",
            created_before="2042-12-31 21:42",
        ))
        self.assertEqual(response.status_code, 200)

    def test_delete_transaction(self):
        # Transactions can't be deleted with a normal usage, but it is possible through the admin interface.
        old_second_user_balance = self.second_user.note.balance
        old_user_balance = self.user.note.balance
        total = self.transaction.total

        self.transaction.delete()
        self.second_user.note.refresh_from_db()
        self.user.note.refresh_from_db()

        self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)
        self.assertTrue(self.user.note.balance == old_user_balance - total)

    def test_calculate_last_negative_duration(self):
        self.assertIsNone(self.user.note.last_negative_duration)
        self.assertIsNotNone(self.second_user.note.last_negative_duration)
        self.assertIsNone(self.club.note.last_negative_duration)

        Transaction.objects.create(
            source=self.club.note,
            destination=self.user.note,
            amount=2 * self.club.note.balance + 100,
            reason="Club balance is negative",
        )

        self.club.note.refresh_from_db()
        self.assertIsNotNone(self.club.note.last_negative_duration)

    def test_api_search(self):
        response = self.client.get("/api/note/note/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get("/api/note/alias/?alias=.*")
        self.assertEqual(response.status_code, 200)
        response = self.client.get("/api/note/consumer/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get("/api/note/transaction/transaction/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get("/api/note/transaction/template/")
        self.assertEqual(response.status_code, 200)

    def test_api_alias(self):
        response = self.client.post("/api/note/alias/", data=dict(
            name="testalias",
            note=self.user.note.id,
        ))
        self.assertEqual(response.status_code, 201)
        self.assertTrue(Alias.objects.filter(name="testalias").exists())
        alias = Alias.objects.get(name="testalias")
        response = self.client.patch("/api/note/alias/" + str(alias.pk) + "/", dict(name="test_updated_alias"),
                                     content_type="application/json")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
        response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
        self.assertEqual(response.status_code, 204)