from decimal import Decimal
from unittest import mock

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase

from gestioncof.models import CofProfile

from ..models import Account, Article, ArticleCategory, Checkout, Operation
from .testcases import TestCaseMixin
from .utils import (
    create_operation_group,
    create_root,
    create_team,
    create_user,
    get_perms,
    user_add_perms,
)

User = get_user_model()


class UserHelpersTests(TestCaseMixin, TestCase):
    def test_create_user(self):
        """create_user creates a basic user and its account."""
        u = create_user()
        a = u.profile.account_kfet

        self.assertInstanceExpected(
            u, {"get_full_name": "first last", "username": "user"}
        )
        self.assertFalse(u.user_permissions.exists())

        self.assertEqual("000", a.trigramme)

    def test_create_team(self):
        u = create_team()
        a = u.profile.account_kfet

        self.assertInstanceExpected(
            u, {"get_full_name": "team member", "username": "team"}
        )
        self.assertTrue(u.has_perm("kfet.is_team"))

        self.assertEqual("100", a.trigramme)

    def test_create_root(self):
        u = create_root()
        a = u.profile.account_kfet

        self.assertInstanceExpected(
            u,
            {
                "get_full_name": "super user",
                "username": "root",
                "is_superuser": True,
                "is_staff": True,
            },
        )

        self.assertEqual("200", a.trigramme)


class PermHelpersTest(TestCaseMixin, TestCase):
    def setUp(self):
        cts = ContentType.objects.get_for_models(Account, CofProfile)
        self.perm1 = Permission.objects.create(
            content_type=cts[Account], codename="test_perm", name="Perm for test"
        )
        self.perm2 = Permission.objects.create(
            content_type=cts[CofProfile],
            codename="another_test_perm",
            name="Another one",
        )
        self.perm_team = Permission.objects.get(
            content_type__app_label="kfet", codename="is_team"
        )

    def test_get_perms(self):
        perms = get_perms("kfet.test_perm", "gestioncof.another_test_perm")
        self.assertDictEqual(
            perms,
            {"kfet.test_perm": self.perm1, "gestioncof.another_test_perm": self.perm2},
        )

    def test_user_add_perms(self):
        user = User.objects.create_user(username="user", password="user")
        user.user_permissions.add(self.perm1)

        user_add_perms(user, ["kfet.is_team", "gestioncof.another_test_perm"])

        self.assertQuerysetEqual(
            user.user_permissions.all(),
            map(repr, [self.perm1, self.perm2, self.perm_team]),
            transform=repr,
            ordered=False,
        )


class OperationHelpersTest(TestCase):
    def test_create_operation_group(self):
        operation_group = create_operation_group()

        on_acc = Account.objects.get(cofprofile__user__username="user")
        checkout = Checkout.objects.get(name="Checkout")
        self.assertDictEqual(
            operation_group.__dict__,
            {
                "_state": mock.ANY,
                "amount": 0,
                "at": mock.ANY,
                "checkout_id": checkout.pk,
                "comment": "",
                "id": mock.ANY,
                "is_cof": False,
                "on_acc_id": on_acc.pk,
                "valid_by_id": None,
            },
        )
        self.assertFalse(operation_group.opes.all())

    def test_create_operation_group_with_content(self):
        article_category = ArticleCategory.objects.create(name="Category")
        article1 = Article.objects.create(
            category=article_category, name="Article 1", price=Decimal("2.50")
        )
        article2 = Article.objects.create(
            category=article_category, name="Article 2", price=Decimal("4.00")
        )
        operation_group = create_operation_group(
            content=[
                {
                    "type": Operation.PURCHASE,
                    "amount": Decimal("-3.50"),
                    "article": article1,
                    "article_nb": 2,
                },
                {"type": Operation.PURCHASE, "article": article2, "article_nb": 2},
                {"type": Operation.PURCHASE, "article": article2},
                {"type": Operation.DEPOSIT, "amount": Decimal("10.00")},
                {"type": Operation.WITHDRAW, "amount": Decimal("-1.00")},
                {"type": Operation.EDIT, "amount": Decimal("7.00")},
            ]
        )

        self.assertEqual(operation_group.amount, Decimal("0.50"))

        operation_list = list(operation_group.opes.all())
        # Passed args: with purchase, article, article_nb, amount
        self.assertEqual(operation_list[0].type, Operation.PURCHASE)
        self.assertEqual(operation_list[0].article, article1)
        self.assertEqual(operation_list[0].article_nb, 2)
        self.assertEqual(operation_list[0].amount, Decimal("-3.50"))
        # Passed args: with purchase, article, article_nb; without amount
        self.assertEqual(operation_list[1].type, Operation.PURCHASE)
        self.assertEqual(operation_list[1].article, article2)
        self.assertEqual(operation_list[1].article_nb, 2)
        self.assertEqual(operation_list[1].amount, Decimal("-8.00"))
        # Passed args: with purchase, article; without article_nb, amount
        self.assertEqual(operation_list[2].type, Operation.PURCHASE)
        self.assertEqual(operation_list[2].article, article2)
        self.assertEqual(operation_list[2].article_nb, 1)
        self.assertEqual(operation_list[2].amount, Decimal("-4.00"))
        # Passed args: with deposit, amount
        self.assertEqual(operation_list[3].type, Operation.DEPOSIT)
        self.assertEqual(operation_list[3].amount, Decimal("10.00"))
        # Passed args: with withdraw, amount
        self.assertEqual(operation_list[4].type, Operation.WITHDRAW)
        self.assertEqual(operation_list[4].amount, Decimal("-1.00"))
        # Passed args: with edit, amount
        self.assertEqual(operation_list[5].type, Operation.EDIT)
        self.assertEqual(operation_list[5].amount, Decimal("7.00"))
