import calendar
from collections import namedtuple
from datetime import date, datetime, timedelta

from django.db import models
from django.urls import reverse_lazy as reverse
from django.utils.dateformat import DateFormat
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from djangocms_text_ckeditor.fields import HTMLField

from ..utils import attributes
from .roles import Leader
from .startend import StartEndMixin


def start_end_by_date(d):
    # we work with monthly timesheets by default
    # TODO: allow weekly and quarterly timesheets by settings
    return {
        "start": date(d.year, d.month, 1),
        "end": date(d.year, d.month, calendar.monthrange(d.year, d.month)[1]),
    }


class TimesheetPeriodManager(models.Manager):
    def for_date(self, date):
        return self.get_or_create(**start_end_by_date(date))[0]


class TimesheetPeriod(StartEndMixin, models.Model):
    start = models.DateField(_("start date"), editable=False, unique=True)
    end = models.DateField(_("end date"), editable=False, unique=True)

    objects = TimesheetPeriodManager()

    class Meta:
        app_label = "leprikon"
        ordering = ("-start",)
        verbose_name = _("timesheet period")
        verbose_name_plural = _("timesheet periods")

    def __str__(self):
        return self.name

    @cached_property
    def name(self):
        # we work with monthly timesheets by default
        # TODO: allow weekly and quarterly timesheets by settings
        return DateFormat(self.start).format("F Y")

    @cached_property
    def all_timesheets(self):
        return list(self.timesheets.all())


class TimesheetManager(models.Manager):
    def for_leader_and_date(self, leader, date):
        return self.get_or_create(
            leader=leader,
            period=TimesheetPeriod.objects.for_date(date),
        )[0]


class Timesheet(models.Model):
    period = models.ForeignKey(
        TimesheetPeriod, verbose_name=_("period"), editable=False, related_name="timesheets", on_delete=models.PROTECT
    )
    leader = models.ForeignKey(
        Leader, verbose_name=_("leader"), editable=False, related_name="timesheets", on_delete=models.PROTECT
    )
    submitted = models.BooleanField(_("submitted"), default=False)
    paid = models.BooleanField(_("paid"), default=False)

    objects = TimesheetManager()

    class Meta:
        app_label = "leprikon"
        ordering = ("-period__start", "leader")
        unique_together = (("period", "leader"),)
        verbose_name = _("timesheet")
        verbose_name_plural = _("timesheets")

    def __str__(self):
        return "{leader}, {period}".format(
            leader=self.leader,
            period=self.period.name,
        )

    @cached_property
    def all_journal_entries(self):
        return list(self.journal_entries.all())

    @cached_property
    def all_timesheet_entries(self):
        return list(self.timesheet_entries.all())

    @cached_property
    def all_entries(self):
        return sorted(
            self.all_timesheet_entries + self.all_journal_entries,
            key=lambda e: e.datetime_start,
        )

    class EntryGroup(namedtuple("_EntryGroup", ("name", "entries"))):
        @property
        def duration(self):
            return sum((e.duration for e in self.entries), timedelta())

    @cached_property
    def groups(self):
        gs = {}
        for entry in self.all_entries:
            if entry.group not in gs:
                gs[entry.group] = self.EntryGroup(
                    name=entry.group.name,
                    entries=[],
                )
            gs[entry.group].entries.append(entry)
        return list(gs.values())


class TimesheetEntryType(models.Model):
    name = models.CharField(_("name"), max_length=150)
    order = models.IntegerField(_("order"), blank=True, default=0)

    class Meta:
        app_label = "leprikon"
        ordering = ("order",)
        verbose_name = _("timesheet entry type")
        verbose_name_plural = _("timesheet entry types")

    def __str__(self):
        return self.name


class TimesheetEntry(StartEndMixin, models.Model):
    timesheet = models.ForeignKey(
        Timesheet,
        verbose_name=_("timesheet"),
        editable=False,
        related_name="timesheet_entries",
        on_delete=models.PROTECT,
    )
    entry_type = models.ForeignKey(
        TimesheetEntryType, verbose_name=_("entry type"), null=True, related_name="entries", on_delete=models.PROTECT
    )
    date = models.DateField(_("date"))
    start = models.TimeField(_("start time"))
    end = models.TimeField(_("end time"))
    description = HTMLField(_("work description"))

    class Meta:
        app_label = "leprikon"
        ordering = ("start",)
        verbose_name = _("timesheet entry")
        verbose_name_plural = _("timesheet entries")

    def __str__(self):
        return "{}".format(self.duration)

    @cached_property
    def datetime_start(self):
        return datetime.combine(self.date, self.start)

    @cached_property
    def datetime_end(self):
        return datetime.combine(self.date, self.end)

    @attributes(short_description=_("duration"))
    @cached_property
    def duration(self):
        return self.datetime_end - self.datetime_start

    @property
    def group(self):
        return self.entry_type

    def get_edit_url(self):
        return reverse("leprikon:timesheetentry_update", args=(self.id,))

    def get_delete_url(self):
        return reverse("leprikon:timesheetentry_delete", args=(self.id,))
