"""Models to test the ``Duplicable`` base model.

Some notes about these models:

A training is the main duplicable model.
A person participate to a specific version of this training. They can have favorite exercises.
The training has an author, and exercises that are organised in plans.
A plan give right to a certificate, which is not duplicable.
Each exercise has one solution, some hints (both duplicable) and have rewards (not duplicable)
Note that some relation don't make any sense but it's to test all possible relations.

"""

from django.db import models

from ...managers import DuplicableManager, DuplicableQuerySet
from ...models import Duplicable


# Do we have to test django-polymorphic specificities
try:
    # Disable pylint "unused-import" because this import is just to set ``TEST_POLYMORPHIC``
    # pylint: disable=W0611
    from polymorphic.polymorphic_model import PolymorphicModel
except ImportError:
    TEST_POLYMORPHIC = False
else:
    TEST_POLYMORPHIC = True


class StrHelper(models.Model):
    """Helper that defines a debug-readable representation of a model instance."""

    # Just to check ``get_non_duplicable_relations``
    non_duplicable_relations = ['foo']

    class Meta:
        abstract = True

    def __str__(self):
        """Display the Pk and, if available, the name."""

        if 'name' in self._meta.get_fields():
            return '#%s (%s)' % (self.pk, self.name)
        else:
            return '#%s' % self.pk


class Training(StrHelper, Duplicable):
    """Root of our ``duplicable`` tree.

    All instances of models that link to this one should be duplicated
    when the instance is duplicated.
    """

    non_duplicable_relations = ['survey_set']

    name = models.CharField(max_length=100)
    author = models.ForeignKey('Person', null=True)
    test_file = models.FileField()


class Survey(StrHelper, Duplicable):
    """A survey is duplicable but not by a training, because in ``non_duplicable_relations``"""
    name = models.CharField(max_length=100)
    training = models.ForeignKey(Training)


class TestQuerySet(DuplicableQuerySet):
    """To test the override of the ``DuplicableQuerySet``"""

    def name_is_foo(self):
        """Return entries with name being "foo"."""

        return self.filter(name='foo')


class TestManager(DuplicableManager.from_queryset(TestQuerySet)):
    """To test the override of the ``DuplicableManager``"""

    def do_nothing(self):
        """Simple method to test if we can access it"""

        pass


class Exercise(StrHelper, Duplicable):
    """An exercise must be duplicated when the training is duplicated."""

    name = models.CharField(max_length=100)
    training = models.ForeignKey(Training)
    rewards = models.ManyToManyField('Reward')

    objects = TestManager()


class Solution(StrHelper, Duplicable):
    """A solution must be duplicated when an exercise is duplicated."""

    name = models.CharField(max_length=100)
    exercise = models.OneToOneField(Exercise, null=True)


class Hint(StrHelper, Duplicable):
    """A hint must be duplicated when an exercise is duplicated."""

    name = models.CharField(max_length=100)
    exercise = models.ForeignKey(Exercise)

    def duplicate(self, **kwargs):
        """Do not duplicate the exercise if we want to duplicate explicitely the hint."""
        return super().duplicate(non_duplicable_relations={'exercise'}, **kwargs)


class Reward(StrHelper):
    """A reward has a M2M to it from the exercise, but must not be duplicated."""

    name = models.CharField(max_length=100)


class Plan(StrHelper, Duplicable):
    """A plan must be duplicated when an exercise of training is duplicated."""

    name = models.CharField(max_length=100)
    exercises = models.ManyToManyField(Exercise, through='PlannedExercise')
    training = models.ForeignKey(Training)


class Certificate(StrHelper):
    """A certificate must NOT be duplicated and is only linked to an original plan.

    Concept: A new certificate must be generated when a plan is updated.
    """

    name = models.CharField(max_length=100)
    plan = models.OneToOneField(Plan)


class PlannedExercise(StrHelper, Duplicable):
    """It's the ``through`` table between ``Plan`` and ``Exercise``.

    It should be duplicated with it exercise and plan.
    """

    plan = models.ForeignKey(Plan)
    exercise = models.ForeignKey(Exercise)
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ('order', )


class Person(StrHelper):
    """A person should not be duplicated.

    The participated trainings should be the real ones, not the duplicated ones.
    Idem for the favorite exercises, they should link to the original ones.

    """

    name = models.CharField(max_length=100)
    participated_trainings = models.ManyToManyField(Training, through='ParticipatedTraining')
    favorite_exercises = models.ManyToManyField('Exercise')


class ParticipatedTraining(StrHelper):
    """It's the ``through`` table between ``Person`` and ``Training``.

    It should not be duplicated, a user participated to a specific version of a training.
    """

    attendee = models.ForeignKey(Person)
    training = models.ForeignKey(Training)
    plan = models.ForeignKey(Plan)
    date = models.DateField()


if TEST_POLYMORPHIC:
    # Some models to test the specific model and manager for polymorphic duplicable models

    from ...managers import PolymorphicManagerForDuplicable, PolymorphicQuerySetForDuplicable
    from ...models import PolymorphicModelForDuplicable


    class Relation(StrHelper, Duplicable):
        """Model to be used as FK from ``PolymorphicBase``."""

        name = models.CharField(max_length=100)


    class PolymorphicBase(StrHelper, Duplicable, PolymorphicModelForDuplicable):
        """Base model to test ``PolymorphicModelForDuplicable``"""

        name = models.CharField(max_length=100)
        relation = models.ForeignKey(Relation, null=True)


    class PolymorphicChild1(PolymorphicBase):
        """First child model to test ``PolymorphicModelForDuplicable``"""

        name1 = models.CharField(max_length=100)


    class TestPolymorphicQuerySet(PolymorphicQuerySetForDuplicable):
        """To test the override of the ``PolymorphicQuerySetForDuplicable``"""

        def name_is_foo(self):
            """Return entries with name being "foo"."""

            return self.filter(name='foo')


    class TestPolymorphicManager(PolymorphicManagerForDuplicable):
        """To test the override of the ``PolymorphicManagerForDuplicable``"""

        queryset_class = TestPolymorphicQuerySet

        def do_nothing(self):
            """Simple method to test if we can access it"""

            pass


    class PolymorphicChild2(PolymorphicBase):
        """Second child model to test ``PolymorphicModelForDuplicable``"""

        name2 = models.CharField(max_length=100)

        objects = TestPolymorphicManager()
