import pytest
from django.db import transaction
from graphene import Context
from graphql_relay import to_global_id

from api.utils import get_graphql_mutation, get_object
from core.factories import TeacherFactory, UserFactory
from core.tests.base_test import SkillboxTestCase
from users.models import SchoolClass, User
from users.services import create_users

UPDATE_SCHOOL_CLASS_MUTATION = get_graphql_mutation("updateSchoolClass.gql")


pytestmark = pytest.mark.django_db


@pytest.fixture
def prefix():
    return "Meine Klasse"


@pytest.mark.usefixtures("create_users")
def test_default_class_name_initial(prefix):
    class_name = SchoolClass.generate_default_group_name()
    assert "{} 1".format(prefix) == class_name


@pytest.mark.usefixtures("create_users")
def test_default_class_name_initial_with_similar_existing(prefix):
    SchoolClass.objects.create(name="{} abc212".format(prefix))
    class_name = SchoolClass.generate_default_group_name()
    assert "{} 1".format(prefix) == class_name


@pytest.mark.usefixtures("create_users")
def test_default_class_name_if_existing(prefix):
    SchoolClass.objects.create(name="{} 1".format(prefix))
    SchoolClass.objects.create(name="{} 10".format(prefix))
    class_name = SchoolClass.generate_default_group_name()
    assert "{} 11".format(prefix) == class_name


@pytest.mark.usefixtures("create_users")
def test_default_class_name_with_user(prefix):
    user = UserFactory(username="hansli")
    class_name = SchoolClass.generate_default_group_name(user=user)
    assert f"{prefix} {user.pk}" == class_name


@pytest.mark.usefixtures("create_users")
class ModifySchoolClassPyTest:
    def test_update_school_class(self, school_class, get_client):
        class_name = "The Colbert Show"

        assert school_class.name == "skillbox"
        id = to_global_id("SchoolClassNode", school_class.id)

        client = get_client()
        result = client.execute(
            UPDATE_SCHOOL_CLASS_MUTATION,
            variables={"input": {"id": id, "name": class_name}},
        )
        assert result.errors is None

        school_class = get_object(SchoolClass, id)
        assert school_class.name == class_name


class ModifySchoolClassTest(SkillboxTestCase):
    def setUp(self):
        create_users()
        self.teacher = User.objects.get(username="teacher")
        self.student = User.objects.get(username="student1")

        self.client = self.get_client(user=self.teacher)
        self.student_client = self.get_client(user=self.student)

    def test_update_school_class(self):
        class_name = "The Colbert Show"

        school_class = SchoolClass.objects.get(name="skillbox")
        self.assertEqual(school_class.name, "skillbox")
        id = to_global_id("SchoolClassNode", school_class.id)

        result = self.client.execute(
            UPDATE_SCHOOL_CLASS_MUTATION,
            variables={"input": {"id": id, "name": class_name}},
        )
        self.assertIsNone(result.errors)

        school_class = get_object(SchoolClass, id)
        self.assertEqual(school_class.name, class_name)

    def test_update_school_class_not_in_class_fails(self):
        client = self.get_client()
        teacher = TeacherFactory(username="conan")
        context = Context(user=teacher)
        school_class = SchoolClass.objects.get(name="skillbox")
        self.assertEqual(school_class.name, "skillbox")
        id = to_global_id("SchoolClassNode", school_class.id)
        variables = {"input": {"id": id, "name": "Nein"}}
        result = client.execute(
            UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context_value=context
        )
        self.assertIsNone(result.errors)

    def test_update_school_class_fail(self):
        class_name = "Nanana"

        school_class = SchoolClass.objects.get(name="skillbox")
        self.assertEqual(school_class.name, "skillbox")
        id = to_global_id("SchoolClassNode", school_class.id)

        result = self.student_client.execute(
            UPDATE_SCHOOL_CLASS_MUTATION,
            variables={"input": {"id": id, "name": class_name}},
        )
        self.assertIsNotNone(result.errors)

    def test_create_school_class(self):
        self.assertEqual(SchoolClass.objects.count(), 2)
        class_name = "Moordale"
        mutation = get_graphql_mutation("createClass.gql")
        query_result = self.client.execute(
            mutation, variables={"input": {"name": class_name}}
        )
        self.assertIsNone(query_result.errors)
        result = query_result.data.get("createSchoolClass").get("result")
        self.assertEqual(result.get("__typename"), "SchoolClassNode")
        id = result.get("id")
        self.assertEqual(SchoolClass.objects.count(), 3)
        school_class = get_object(SchoolClass, id)
        self.assertEqual(school_class.name, class_name)
        self.assertEqual(school_class.get_teachers(), [self.teacher])
        self.assertEqual(self.teacher.selected_class.name, class_name)

    def test_create_school_class_duplicate_name_fail(self):
        self.assertEqual(SchoolClass.objects.count(), 2)
        class_name = "skillbox"
        mutation = get_graphql_mutation("createClass.gql")
        # if we don't do this, django wraps the whole test in an atomic operation,
        # and we trigger an exception so the query later in the test would fail
        with transaction.atomic():
            query_result = self.client.execute(
                mutation, variables={"input": {"name": class_name}}
            )
        self.assertIsNone(query_result.errors)
        result = query_result.data.get("createSchoolClass").get("result")
        self.assertEqual(result.get("__typename"), "DuplicateName")
        reason = result.get("reason")
        self.assertEqual(reason, "Dieser Name wird bereits verwendet.")
        self.assertEqual(SchoolClass.objects.count(), 2)

    def test_create_school_class_fail_permission(self):
        self.assertEqual(SchoolClass.objects.count(), 2)
        mutation = get_graphql_mutation("createClass.gql")
        result = self.student_client.execute(
            mutation, variables={"input": {"name": "No School"}}
        )
        self.assertIsNotNone(result.errors)
