# coding: utf-8
from django.db import models
from django.forms.models import model_to_dict

from .managers import PublicItemManager
from .utils import truncate_string


class TimeStampedModelMixin(models.Model):
    "Should be mixed in to all models."
    time_created = models.DateTimeField(
        auto_now_add=True, help_text="The time this item was created in the database."
    )
    time_modified = models.DateTimeField(
        auto_now=True, help_text="The time this item was last saved to the database."
    )

    class Meta:
        abstract = True


class DiffModelMixin(object):
    """A model mixin that tracks model fields' values and provide some useful
    api to know what fields have been changed.

    eg:
    Get an object from the database.
    Set some of its properties.
    Call `myObj.has_changed` to see if any fields are different to in the DB.

    From http://stackoverflow.com/a/13842223/250962
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__initial = self._dict

    @property
    def diff(self):
        """Returns a dict of properties that have changed, with values a list
        of before/after changes. eg:
        `{'categories': (None, [1, 3, 5]), 'rank': (0, 42)}`
        """
        d1 = self.__initial
        d2 = self._dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
        return dict(diffs)

    @property
    def has_changed(self):
        "Returns True if any of the properties have changed."
        return bool(self.diff)

    @property
    def changed_fields(self):
        "Returns a list of property names for any that have changed."
        return self.diff.keys()

    def get_field_diff(self, field_name):
        "Returns a diff for field if it's changed and None otherwise."
        return self.diff.get(field_name, None)

    def save(self, *args, **kwargs):
        "Saves model and set initial state."
        super().save(*args, **kwargs)
        self.__initial = self._dict

    @property
    def _dict(self):
        return model_to_dict(self, fields=[field.name for field in self._meta.fields])


class DittoItemModel(TimeStampedModelMixin, DiffModelMixin, models.Model):
    """
    A content item on whatever service we're copying.
    eg, a Tweet, a Photo, a Bookmark, etc.

    Should be inherited by a model in child apps.
    eg, Tweet, Photo, Bookmark, etc.
    """

    # Should be overridden for child classes.
    # eg, 'flickr_photo', 'twitter_tweet', etc.
    # Used in templates.
    ditto_item_name = "set__ditto_item_name__in_child_class"

    title = models.CharField(blank=True, max_length=255)
    permalink = models.URLField(
        blank=True, help_text="URL of the item on the service's website."
    )
    # Ensures that all children have a common short piece of text for display:
    summary = models.CharField(
        blank=True,
        max_length=255,
        help_text=(
            "eg, Brief summary or excerpt of item's text content. "
            "No linebreaks or HTML."
        ),
    )
    is_private = models.BooleanField(
        default=False,
        help_text="If true, this item will not be shown on public-facing pages.",
    )
    fetch_time = models.DateTimeField(
        null=True, blank=True, help_text="The time the item's data was last fetched."
    )

    post_time = models.DateTimeField(
        null=True,
        blank=True,
        db_index=True,
        help_text="The time the item was originally posted/created on its service.",
    )
    post_year = models.PositiveSmallIntegerField(
        null=True, blank=True, db_index=True, help_text="Set automatically on save"
    )

    # Obviously not relevant to some items, like Bookmarks.
    latitude = models.DecimalField(
        null=True, blank=True, max_digits=9, decimal_places=6
    )
    longitude = models.DecimalField(
        null=True, blank=True, max_digits=9, decimal_places=6
    )

    raw = models.TextField(blank=True, help_text="eg, the raw JSON from the API.")

    # All Items (eg, used in Admin):
    objects = models.Manager()

    # All Items which aren't private. Should ALWAYS be used for public pages:
    public_objects = PublicItemManager()

    class Meta:
        abstract = True
        get_latest_by = "post_time"

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if "update_fields" in kwargs:
            # Needed to ensure summary and post_year are also saved
            kwargs["update_fields"].add("summary")
            kwargs["update_fields"].add("post_year")
        self.summary = self._make_summary()
        if self.post_time:
            self.post_year = self.post_time.year
        else:
            self.post_year = None
        super().save(*args, **kwargs)

    def _summary_source(self):
        """
        Child classes can return a string that's used to make the truncated,
        HTML-free `summary`.
        e.g.:
            return self.description
        """
        return ""

    def _make_summary(self):
        """
        Returns the string to be used for the `summary` property.
        """
        return (
            truncate_string(
                self._summary_source(),
                strip_html=True,
                chars=255,
                truncate="…",
                at_word_boundary=True,
            )
            .replace("\n", " ")
            .replace("\r", " ")
        )
