import json
from datetime import timedelta
from unittest import mock

from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import formats, timezone

from ..models import Participant, Tirage
from .mixins import BdATestHelpers, BdAViewTestCaseMixin

User = get_user_model()


class InscriptionViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-tirage-inscription"

    http_methods = ["GET", "POST"]

    auth_user = "bda_member"
    auth_forbidden = [None, "bda_other"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/inscription/{}".format(self.tirage.id)

    def test_get_opened(self):
        self.tirage.ouverture = timezone.now() - timedelta(days=1)
        self.tirage.fermeture = timezone.now() + timedelta(days=1)
        self.tirage.save()

        resp = self.client.get(self.url)

        self.assertEqual(resp.status_code, 200)
        self.assertFalse(resp.context["messages"])

    def test_get_closed_future(self):
        self.tirage.ouverture = timezone.now() + timedelta(days=1)
        self.tirage.fermeture = timezone.now() + timedelta(days=2)
        self.tirage.save()

        resp = self.client.get(self.url)

        self.assertEqual(resp.status_code, 200)
        self.assertIn(
            "Le tirage n'est pas encore ouvert : ouverture le {}".format(
                formats.localize(timezone.template_localtime(self.tirage.ouverture))
            ),
            [str(msg) for msg in resp.context["messages"]],
        )

    def test_get_closed_past(self):
        self.tirage.ouverture = timezone.now() - timedelta(days=2)
        self.tirage.fermeture = timezone.now() - timedelta(days=1)
        self.tirage.save()

        resp = self.client.get(self.url)

        self.assertEqual(resp.status_code, 200)
        self.assertIn(
            " C'est fini : tirage au sort dans la journée !",
            [str(msg) for msg in resp.context["messages"]],
        )

    def get_base_post_data(self):
        return {
            "choixspectacle_set-TOTAL_FORMS": "3",
            "choixspectacle_set-INITIAL_FORMS": "0",
            "choixspectacle_set-MIN_NUM_FORMS": "0",
            "choixspectacle_set-MAX_NUM_FORMS": "1000",
        }

    base_post_data = property(get_base_post_data)

    def test_post(self):
        self.tirage.ouverture = timezone.now() - timedelta(days=1)
        self.tirage.fermeture = timezone.now() + timedelta(days=1)
        self.tirage.save()

        data = dict(
            self.base_post_data,
            **{
                "choixspectacle_set-TOTAL_FORMS": "2",
                "choixspectacle_set-0-id": "",
                "choixspectacle_set-0-participant": "",
                "choixspectacle_set-0-spectacle": str(self.show1.pk),
                "choixspectacle_set-0-double_choice": "1",
                "choixspectacle_set-0-priority": "2",
                "choixspectacle_set-1-id": "",
                "choixspectacle_set-1-participant": "",
                "choixspectacle_set-1-spectacle": str(self.show2.pk),
                "choixspectacle_set-1-double_choice": "autoquit",
                "choixspectacle_set-1-priority": "1",
            }
        )
        resp = self.client.post(self.url, data)

        self.assertEqual(resp.status_code, 200)
        self.assertIn(
            "Votre inscription a été mise à jour avec succès !",
            [str(msg) for msg in resp.context["messages"]],
        )
        participant = Participant.objects.get(
            user=self.users["bda_member"], tirage=self.tirage
        )
        self.assertSetEqual(
            set(
                participant.choixspectacle_set.values_list(
                    "priority", "spectacle_id", "double_choice"
                )
            ),
            {(1, self.show2.pk, "autoquit"), (2, self.show1.pk, "1")},
        )

    def test_post_state_changed(self):
        self.tirage.ouverture = timezone.now() - timedelta(days=1)
        self.tirage.fermeture = timezone.now() + timedelta(days=1)
        self.tirage.save()

        data = {"dbstate": "different"}
        resp = self.client.post(self.url, data)

        self.assertEqual(resp.status_code, 200)
        self.assertIn(
            "Impossible d'enregistrer vos modifications : vous avez apporté d'autres "
            "modifications entre temps.",
            [str(msg) for msg in resp.context["messages"]],
        )


class PlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-places-attribuees"

    auth_user = "bda_member"
    auth_forbidden = [None, "bda_other"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/places/{}".format(self.tirage.id)


class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-etat-places"

    auth_user = "bda_member"
    auth_forbidden = [None, "bda_other"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/etat-places/{}".format(self.tirage.id)


class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-tirage"

    http_methods = ["GET", "POST"]

    auth_user = "bda_staff"
    auth_forbidden = [None, "bda_other", "bda_member"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/tirage/{}".format(self.tirage.id)

    def test_perform_tirage_disabled(self):
        # Cannot be performed if disabled
        self.tirage.enable_do_tirage = False
        self.tirage.save()
        resp = self.client.get(self.url)
        self.assertTemplateUsed(resp, "tirage-failed.html")

    def test_perform_tirage_opened_registrations(self):
        # Cannot be performed if registrations are still open
        self.tirage.enable_do_tirage = True
        self.tirage.fermeture = timezone.now() + timedelta(seconds=3600)
        self.tirage.save()
        resp = self.client.get(self.url)
        self.assertTemplateUsed(resp, "tirage-failed.html")

    def test_perform_tirage(self):
        # Otherwise, perform the tirage
        self.tirage.enable_do_tirage = True
        self.tirage.fermeture = timezone.now()
        self.tirage.save()
        resp = self.client.get(self.url)
        self.assertTemplateNotUsed(resp, "tirage-failed.html")


class SpectacleListViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-liste-spectacles"

    auth_user = "bda_staff"
    auth_forbidden = [None, "bda_other", "bda_member"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/spectacles/{}".format(self.tirage.id)


class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-spectacle"

    auth_user = "bda_staff"
    auth_forbidden = [None, "bda_other", "bda_member"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id, "spectacle_id": self.show1.id}

    @property
    def url_expected(self):
        return "/gestion/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id)


class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-unpaid"

    auth_user = "bda_staff"
    auth_forbidden = [None, "bda_other", "bda_member"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"tirage_id": self.tirage.id}

    @property
    def url_expected(self):
        return "/gestion/bda/spectacles/unpaid/{}".format(self.tirage.id)


class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    url_name = "bda-rappels"

    auth_user = "bda_staff"
    auth_forbidden = [None, "bda_other", "bda_member"]

    bda_testdata = True

    @property
    def url_kwargs(self):
        return {"spectacle_id": self.show1.id}

    @property
    def url_expected(self):
        return "/gestion/bda/mails-rappel/{}".format(self.show1.id)

    def test_post(self):
        resp = self.client.post(self.url)
        self.assertEqual(200, resp.status_code)
        # TODO: check that emails are sent


class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
    auth_user = None
    auth_forbidden = []

    bda_testdata = True

    def test_api_list(self):
        url_list = "/gestion/bda/catalogue/list"
        resp = self.client.get(url_list)
        self.assertJSONEqual(
            resp.content.decode("utf-8"),
            [{"id": self.tirage.id, "title": self.tirage.title}],
        )

    def test_api_details(self):
        url_details = "/gestion/bda/catalogue/details?id={}".format(self.tirage.id)
        resp = self.client.get(url_details)
        self.assertJSONEqual(
            resp.content.decode("utf-8"),
            {
                "categories": [{"id": self.category.id, "name": self.category.name}],
                "locations": [{"id": self.location.id, "name": self.location.name}],
            },
        )

    def test_api_descriptions(self):
        url_descriptions = "/gestion/bda/catalogue/descriptions?id={}".format(
            self.tirage.id
        )
        resp = self.client.get(url_descriptions)
        raw = resp.content.decode("utf-8")
        try:
            results = json.loads(raw)
        except ValueError:
            self.fail("Not valid JSON: {}".format(raw))
        self.assertEqual(len(results), 3)
        self.assertEqual(
            {(s["title"], s["price"], s["slots"]) for s in results},
            {("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)},
        )


# ----- BdA Revente --------------------------------------- #


def make_participant(name: str, tirage: Tirage) -> User:
    user = User.objects.create_user(username=name, password=name)
    user.profile.is_cof = True
    user.profile.save()
    Participant.objects.create(user=user, tirage=tirage)
    return user


class TestReventeManageTest(TestCase):
    def setUp(self):
        self.tirage = Tirage.objects.create(
            title="tirage1",
            ouverture=timezone.now(),
            fermeture=timezone.now() + timedelta(days=90),
        )
        self.user = make_participant("toto", self.tirage)
        self.url = reverse("bda-revente-manage", args=[self.tirage.id])

        # Signals handlers on login/logout send messages.
        # Due to the way the Django' test Client performs login, this raise an
        # error. As workaround, we mock the Django' messages module.
        patcher_messages = mock.patch("gestioncof.signals.messages")
        patcher_messages.start()
        self.addCleanup(patcher_messages.stop)

    def test_can_get(self):
        client = Client()
        client.force_login(
            self.user, backend="django.contrib.auth.backends.ModelBackend"
        )
        r = client.get(self.url)
        self.assertEqual(r.status_code, 200)


class TestBdaRevente:
    pass
    # TODO
