import logging
import os

from django.conf import settings as conf
from django.db import models
from django.db.models import Q
from django.utils import timezone as tz
from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager

from aircox.conf import settings

from .episode import Episode
from .program import Program

logger = logging.getLogger("aircox")


__all__ = ("Sound", "SoundQuerySet", "Track")


class SoundQuerySet(models.QuerySet):
    def station(self, station=None, id=None):
        id = station.pk if id is None else id
        return self.filter(program__station__id=id)

    def episode(self, episode=None, id=None):
        id = episode.pk if id is None else id
        return self.filter(episode__id=id)

    def diffusion(self, diffusion=None, id=None):
        id = diffusion.pk if id is None else id
        return self.filter(episode__diffusion__id=id)

    def available(self):
        return self.exclude(type=Sound.TYPE_REMOVED)

    def public(self):
        """Return sounds available as podcasts."""
        return self.filter(is_public=True)

    def downloadable(self):
        """Return sounds available as podcasts."""
        return self.filter(is_downloadable=True)

    def archive(self):
        """Return sounds that are archives."""
        return self.filter(type=Sound.TYPE_ARCHIVE)

    def path(self, paths):
        if isinstance(paths, str):
            return self.filter(file=paths.replace(conf.MEDIA_ROOT + "/", ""))
        return self.filter(
            file__in=(p.replace(conf.MEDIA_ROOT + "/", "") for p in paths)
        )

    def playlist(self, archive=True, order_by=True):
        """Return files absolute paths as a flat list (exclude sound without
        path).

        If `order_by` is True, order by path.
        """
        if archive:
            self = self.archive()
        if order_by:
            self = self.order_by("file")
        return [
            os.path.join(conf.MEDIA_ROOT, file)
            for file in self.filter(file__isnull=False).values_list(
                "file", flat=True
            )
        ]

    def search(self, query):
        return self.filter(
            Q(name__icontains=query)
            | Q(file__icontains=query)
            | Q(program__title__icontains=query)
            | Q(episode__title__icontains=query)
        )


# TODO:
# - provide a default name based on program and episode
class Sound(models.Model):
    """A Sound is the representation of a sound file that can be either an
    excerpt or a complete archive of the related diffusion."""

    TYPE_OTHER = 0x00
    TYPE_ARCHIVE = 0x01
    TYPE_EXCERPT = 0x02
    TYPE_REMOVED = 0x03
    TYPE_CHOICES = (
        (TYPE_OTHER, _("other")),
        (TYPE_ARCHIVE, _("archive")),
        (TYPE_EXCERPT, _("excerpt")),
        (TYPE_REMOVED, _("removed")),
    )

    name = models.CharField(_("name"), max_length=64)
    program = models.ForeignKey(
        Program,
        models.CASCADE,
        blank=True,  # NOT NULL
        verbose_name=_("program"),
        help_text=_("program related to it"),
        db_index=True,
    )
    episode = models.ForeignKey(
        Episode,
        models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_("episode"),
        db_index=True,
    )
    type = models.SmallIntegerField(_("type"), choices=TYPE_CHOICES)
    position = models.PositiveSmallIntegerField(
        _("order"),
        default=0,
        help_text=_("position in the playlist"),
    )

    def _upload_to(self, filename):
        subdir = (
            settings.SOUND_ARCHIVES_SUBDIR
            if self.type == self.TYPE_ARCHIVE
            else settings.SOUND_EXCERPTS_SUBDIR
        )
        return os.path.join(self.program.path, subdir, filename)

    file = models.FileField(
        _("file"),
        upload_to=_upload_to,
        max_length=256,
        db_index=True,
        unique=True,
    )
    duration = models.TimeField(
        _("duration"),
        blank=True,
        null=True,
        help_text=_("duration of the sound"),
    )
    mtime = models.DateTimeField(
        _("modification time"),
        blank=True,
        null=True,
        help_text=_("last modification date and time"),
    )
    is_good_quality = models.BooleanField(
        _("good quality"),
        help_text=_("sound meets quality requirements"),
        blank=True,
        null=True,
    )
    is_public = models.BooleanField(
        _("public"),
        help_text=_("whether it is publicly available as podcast"),
        default=False,
    )
    is_downloadable = models.BooleanField(
        _("downloadable"),
        help_text=_(
            "whether it can be publicly downloaded by visitors (sound must be "
            "public)"
        ),
        default=False,
    )

    objects = SoundQuerySet.as_manager()

    class Meta:
        verbose_name = _("Sound")
        verbose_name_plural = _("Sounds")

    @property
    def url(self):
        return self.file and self.file.url

    def __str__(self):
        return "/".join(self.file.path.split("/")[-3:])

    def save(self, check=True, *args, **kwargs):
        if self.episode is not None and self.program is None:
            self.program = self.episode.program
        if check:
            self.check_on_file()
        if not self.is_public:
            self.is_downloadable = False
        self.__check_name()
        super().save(*args, **kwargs)

    # TODO: rename get_file_mtime(self)
    def get_mtime(self):
        """Get the last modification date from file."""
        mtime = os.stat(self.file.path).st_mtime
        mtime = tz.datetime.fromtimestamp(mtime)
        mtime = mtime.replace(microsecond=0)
        return tz.make_aware(mtime, tz.get_current_timezone())

    def file_exists(self):
        """Return true if the file still exists."""

        return os.path.exists(self.file.path)

    # TODO: rename to sync_fs()
    def check_on_file(self):
        """Check sound file info again'st self, and update informations if
        needed (do not save).

        Return True if there was changes.
        """
        if not self.file_exists():
            if self.type == self.TYPE_REMOVED:
                return
            logger.debug("sound %s: has been removed", self.file.name)
            self.type = self.TYPE_REMOVED
            return True

        # not anymore removed
        changed = False

        if self.type == self.TYPE_REMOVED and self.program:
            changed = True
            self.type = (
                self.TYPE_ARCHIVE
                if self.file.name.startswith(self.program.archives_path)
                else self.TYPE_EXCERPT
            )

        # check mtime -> reset quality if changed (assume file changed)
        mtime = self.get_mtime()

        if self.mtime != mtime:
            self.mtime = mtime
            self.is_good_quality = None
            logger.debug(
                "sound %s: m_time has changed. Reset quality info",
                self.file.name,
            )
            return True

        return changed

    def __check_name(self):
        if not self.name and self.file and self.file.name:
            # FIXME: later, remove date?
            name = os.path.basename(self.file.name)
            name = os.path.splitext(name)[0]
            self.name = name.replace("_", " ").strip()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__check_name()


class Track(models.Model):
    """Track of a playlist of an object.

    The position can either be expressed as the position in the playlist
    or as the moment in seconds it started.
    """

    episode = models.ForeignKey(
        Episode,
        models.CASCADE,
        blank=True,
        null=True,
        verbose_name=_("episode"),
    )
    sound = models.ForeignKey(
        Sound,
        models.CASCADE,
        blank=True,
        null=True,
        verbose_name=_("sound"),
    )
    position = models.PositiveSmallIntegerField(
        _("order"),
        default=0,
        help_text=_("position in the playlist"),
    )
    timestamp = models.PositiveSmallIntegerField(
        _("timestamp"),
        blank=True,
        null=True,
        help_text=_("position (in seconds)"),
    )
    title = models.CharField(_("title"), max_length=128)
    artist = models.CharField(_("artist"), max_length=128)
    album = models.CharField(_("album"), max_length=128, null=True, blank=True)
    tags = TaggableManager(verbose_name=_("tags"), blank=True)
    year = models.IntegerField(_("year"), blank=True, null=True)
    # FIXME: remove?
    info = models.CharField(
        _("information"),
        max_length=128,
        blank=True,
        null=True,
        help_text=_(
            "additional informations about this track, such as "
            "the version, if is it a remix, features, etc."
        ),
    )

    class Meta:
        verbose_name = _("Track")
        verbose_name_plural = _("Tracks")
        ordering = ("position",)

    def __str__(self):
        return "{self.artist} -- {self.title} -- {self.position}".format(
            self=self
        )

    def save(self, *args, **kwargs):
        if (self.sound is None and self.episode is None) or (
            self.sound is not None and self.episode is not None
        ):
            raise ValueError("sound XOR episode is required")
        super().save(*args, **kwargs)
