#  Copyright (c) 2019 Brian Schubert
#
#  This file is distributed under the MIT License. If a copy of the
#  MIT License was not distributed with this file, you can obtain one
#  at https://opensource.org/licenses/MIT.

from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Count
from django.utils.functional import cached_property


class PotQuerySet(models.QuerySet):
    def _count_relation(self, field, annotation_name):
        return self.annotate(**{
            annotation_name: Count(field)
        })

    def with_tea_count(self):
        return self._count_relation('supported_teas', 'tea_count')

    def with_addition_count(self):
        return self._count_relation('supported_additions', 'addition_count')


class Pot(models.Model):
    """A Tea- or Coffee Pot capable of brewing a choice beverage."""
    name = models.CharField(
        max_length=35,
        unique=True,
        help_text='The name of this pot, e.g. "Joe\'s Joe Jar" or "Breville (R)'
                  ' BTM800XL"',
    )

    brew_coffee = models.BooleanField(
        verbose_name='able to brew coffee',
        default=True,
        help_text="Can this pot brew coffee?",
    )

    supported_teas = models.ManyToManyField(
        'TeaType',
        blank=True,
        related_name='pot_list'
    )

    supported_additions = models.ManyToManyField(
        'Addition',
        blank=True,
        related_name='pot_list'
    )

    objects = PotQuerySet.as_manager()

    def __str__(self):
        return '{} - {}'.format(self.id, self.name)

    @cached_property
    def tea_capable(self):
        """Return True if this pot can serve tea."""
        return self.supported_teas.exists()

    @property
    def is_teapot(self):
        """Return True if this pot can serve tea, but cannot serve coffee."""
        return self.tea_capable and not self.brew_coffee

    @cached_property
    def supported_milks(self):
        """
        Return the set of names of the milk-type Additions that this pots
        supports.
        """
        # Perform the filtering for milk-type additions in Python so that this
        # method will makes use of a cached query set (perhaps from a call to
        # QuerySet.prefetch_related()), if one exists.
        return set(a.name for a in self.supported_additions.all() if a.type == Addition.MILK)

    def serves_additions(self, additions):
        """Return True if this pot can serve the specified additions."""
        # Select Addition names using a generator instead of calling
        # QuerySet.values_list() so that this method will makes use of a
        # cached query set (perhaps from a call to QuerySet.prefetch_related()),
        # if one exists.
        supported = (a.name for a in self.supported_additions.all())
        return set(additions).issubset(supported)


class TeaType(models.Model):
    """
    A variety of tea that can be brewed by a pot.

    Per the HTCPCP standard, tea may be available as tea bags or tea leaves.
    """
    name = models.CharField(
        verbose_name='Tea name',
        max_length=35,
        unique=True,
        db_index=True,
    )

    slug = models.SlugField(unique=True)

    def __str__(self):
        return self.name


class Addition(models.Model):
    """
    A beverage addition that may be specified in the Accept-Additions header
    field of an HTCPCP request.
    """

    MILK = 'MLK'

    SYRUP = 'SYP'

    SWEETENER = 'SWT'

    SPICE = 'SPC'

    ALCOHOL = 'ACL'

    SUGAR = 'SUG'

    OTHER = 'OTR'

    TYPE_CHOICES = (
        (MILK, "Milk"),
        (SYRUP, "Syrup"),
        (SWEETENER, "Sweetener"),
        (SPICE, "Spice"),
        (ALCOHOL, "Alcohol"),
        (SUGAR, "Sugar"),
        (OTHER, "Other"),
    )

    name = models.CharField(
        max_length=35,
        unique=True,
        help_text="The name of this beverage addition as it would appear in the"
                  " HTCPCP Accept-Additions header field.",
    )

    type = models.CharField(
        max_length=3,
        choices=TYPE_CHOICES,
        verbose_name='Addition type',
    )

    def clean(self):
        # Calling parent in case it is given a non-empty body in the
        # future (currently it is empty).
        super().clean()

        error_msg = ("Decaffeinated addition not allowed. RFC 2324 specifies "
                     "no option for decaffeinated coffee. What's the point? ")

        if 'decaffeinated' in self.name.lower():
            raise ValidationError(error_msg)

    def __str__(self):
        return "{} / {}".format(self.get_type_display(), self.name)
