import logging
import os
import shutil

from django.conf import settings as conf
from django.db import models
from django.db.models import F
from django.db.models.functions import Concat, Substr
from django.utils.translation import gettext_lazy as _

from aircox.conf import settings

from .page import Page, PageQuerySet
from .station import Station

logger = logging.getLogger("aircox")


__all__ = (
    "Program",
    "ProgramChildQuerySet",
    "ProgramQuerySet",
    "Stream",
)


class ProgramQuerySet(PageQuerySet):
    def station(self, station):
        # FIXME: reverse-lookup
        return self.filter(station=station)

    def active(self):
        return self.filter(active=True)


class Program(Page):
    """A Program can either be a Streamed or a Scheduled program.

    A Streamed program is used to generate non-stop random playlists when there
    is not scheduled diffusion. In such a case, a Stream is used to describe
    diffusion informations.

    A Scheduled program has a schedule and is the one with a normal use case.

    Renaming a Program rename the corresponding directory to matches the new
    name if it does not exists.
    """

    # explicit foreign key in order to avoid related name clashes
    station = models.ForeignKey(
        Station, models.CASCADE, verbose_name=_("station")
    )
    active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_("if not checked this program is no longer active"),
    )
    sync = models.BooleanField(
        _("syncronise"),
        default=True,
        help_text=_("update later diffusions according to schedule changes"),
    )

    objects = ProgramQuerySet.as_manager()
    detail_url_name = "program-detail"

    @property
    def path(self):
        """Return program's directory path."""
        return os.path.join(settings.PROGRAMS_DIR, self.slug.replace("-", "_"))

    @property
    def abspath(self):
        """Return absolute path to program's dir."""
        return os.path.join(conf.MEDIA_ROOT, self.path)

    @property
    def archives_path(self):
        return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)

    @property
    def excerpts_path(self):
        return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)

    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, **kwargs)
        if self.slug:
            self.__initial_path = self.path
            self.__initial_cover = self.cover

    @classmethod
    def get_from_path(cl, path):
        """Return a Program from the given path.

        We assume the path has been given in a previous time by this
        model (Program.path getter).
        """
        if path.startswith(settings.PROGRAMS_DIR_ABS):
            path = path.replace(settings.PROGRAMS_DIR_ABS, "")
        while path[0] == "/":
            path = path[1:]
        path = path[: path.index("/")]
        return cl.objects.filter(slug=path.replace("_", "-")).first()

    def ensure_dir(self, subdir=None):
        """Make sur the program's dir exists (and optionally subdir).

        Return True if the dir (or subdir) exists.
        """
        path = os.path.join(self.abspath, subdir) if subdir else self.abspath
        os.makedirs(path, exist_ok=True)
        return os.path.exists(path)

    class Meta:
        verbose_name = _("Program")
        verbose_name_plural = _("Programs")

    def __str__(self):
        return self.title

    def save(self, *kargs, **kwargs):
        from .sound import Sound

        super().save(*kargs, **kwargs)

        # TODO: move in signals
        path_ = getattr(self, "__initial_path", None)
        abspath = path_ and os.path.join(conf.MEDIA_ROOT, path_)
        if (
            path_ is not None
            and path_ != self.path
            and os.path.exists(abspath)
            and not os.path.exists(self.abspath)
        ):
            logger.info(
                "program #%s's dir changed to %s - update it.",
                self.id,
                self.title,
            )

            shutil.move(abspath, self.abspath)
            Sound.objects.filter(path__startswith=path_).update(
                file=Concat("file", Substr(F("file"), len(path_)))
            )


class ProgramChildQuerySet(PageQuerySet):
    def station(self, station=None, id=None):
        return (
            self.filter(parent__program__station=station)
            if id is None
            else self.filter(parent__program__station__id=id)
        )

    def program(self, program=None, id=None):
        return self.parent(program, id)


class Stream(models.Model):
    """When there are no program scheduled, it is possible to play sounds in
    order to avoid blanks. A Stream is a Program that plays this role, and
    whose linked to a Stream.

    All sounds that are marked as good and that are under the related
    program's archive dir are elligible for the sound's selection.
    """

    program = models.ForeignKey(
        Program,
        models.CASCADE,
        verbose_name=_("related program"),
    )
    delay = models.TimeField(
        _("delay"),
        blank=True,
        null=True,
        help_text=_("minimal delay between two sound plays"),
    )
    begin = models.TimeField(
        _("begin"),
        blank=True,
        null=True,
        help_text=_("used to define a time range this stream is " "played"),
    )
    end = models.TimeField(
        _("end"),
        blank=True,
        null=True,
        help_text=_("used to define a time range this stream is " "played"),
    )
