from datetime import date, datetime, time, timedelta

import pytest
from model_bakery import baker

import calendar
from dateutil.relativedelta import relativedelta

from aircox import utils
from aircox.models import Diffusion, Schedule


class TestSchedule:
    @pytest.mark.django_db
    def test_save_rerun(self, sched_reruns):
        for schedule in sched_reruns:
            schedule.duration = None
            schedule.frequency = None
            schedule.save_rerun()
            assert schedule.program == schedule.initial.program
            assert schedule.duration == schedule.initial.duration
            assert schedule.frequency == schedule.initial.frequency

    @pytest.mark.django_db
    def test_tz(self, schedules):
        for schedule in schedules:
            assert schedule.timezone == schedule.tz.zone

    @pytest.mark.django_db
    def test_start(self, schedules):
        for schedule in schedules:
            assert schedule.start.date() == schedule.date
            assert schedule.start.time() == schedule.time

    @pytest.mark.django_db
    def test_end(self, schedules):
        for schedule in schedules:
            delta = utils.to_timedelta(schedule.duration)
            assert schedule.end - schedule.start == delta

    # def test_get_frequency_display(self):
    #    pass

    @pytest.mark.django_db
    def test_normalize(self, schedules):
        for schedule in schedules:
            dt = datetime.combine(schedule.date, schedule.time)
            assert schedule.normalize(dt).tzinfo.zone == schedule.timezone

    @pytest.mark.django_db
    def test_dates_of_month_ponctual(self):
        schedule = baker.prepare(
            Schedule, frequency=Schedule.Frequency.ponctual
        )
        at = schedule.date + relativedelta(months=4)
        assert schedule.dates_of_month(at) == []

    @pytest.mark.django_db
    @pytest.mark.parametrize("months", range(0, 25, 4))
    @pytest.mark.parametrize("hour", range(0, 24, 4))
    def test_dates_of_month_last(self, months, hour):
        schedule = baker.prepare(
            Schedule, time=time(hour, 00), frequency=Schedule.Frequency.last
        )
        at = schedule.date + relativedelta(months=months)
        datetimes = schedule.dates_of_month(at)
        assert len(datetimes) == 1

        dt = datetimes[0]
        self._assert_date(schedule, at, dt)

        month_info = calendar.monthrange(at.year, at.month)
        at = date(at.year, at.month, month_info[1])
        if at.weekday() < schedule.date.weekday():
            at -= timedelta(days=7)
        at += timedelta(days=schedule.date.weekday()) - timedelta(
            days=at.weekday()
        )
        assert dt.date() == at

    # since the same method is used for first, second, etc. frequencies
    # we assume testing every is sufficient
    @pytest.mark.django_db
    @pytest.mark.parametrize("months", range(0, 25, 4))
    @pytest.mark.parametrize("hour", range(0, 24, 4))
    def test_dates_of_month_every(self, months, hour):
        schedule = baker.prepare(
            Schedule, time=time(hour, 00), frequency=Schedule.Frequency.every
        )
        at = schedule.date + relativedelta(months=months)
        datetimes = schedule.dates_of_month(at)
        last = None
        for dt in datetimes:
            self._assert_date(schedule, at, dt)
            if last:
                assert (dt - last).days == 7
            last = dt

    @pytest.mark.django_db
    @pytest.mark.parametrize("months", range(0, 25, 4))
    @pytest.mark.parametrize("hour", range(0, 24, 4))
    def test_dates_of_month_one_on_two(self, months, hour):
        schedule = baker.prepare(
            Schedule,
            time=time(hour, 00),
            frequency=Schedule.Frequency.one_on_two,
        )
        at = schedule.date + relativedelta(months=months)
        datetimes = schedule.dates_of_month(at)
        for dt in datetimes:
            self._assert_date(schedule, at, dt)
            delta = dt.date() - schedule.date
            assert delta.days % 14 == 0

    def _assert_date(self, schedule, at, dt):
        assert dt.year == at.year
        assert dt.month == at.month
        assert dt.weekday() == schedule.date.weekday()
        assert dt.time() == schedule.time
        assert dt.tzinfo.zone == schedule.timezone

    @pytest.mark.django_db
    def test_diffusions_of_month(self, sched_initials):
        # TODO: test values of initial, rerun
        for schedule in sched_initials:
            at = schedule.date + timedelta(days=30)
            dates = set(schedule.dates_of_month(at))
            episodes, diffusions = schedule.diffusions_of_month(at)

            assert all(r.date in dates for r in episodes)
            assert all(
                (not r.initial or r.date in dates)
                and r.type == Diffusion.TYPE_ON_AIR
                for r in diffusions
            )
