
from datetime import datetime
from unittest import TestCase

from bot_scheduler.scheduling.probabilistic import (
    CalendarOccurrenceRates,
    HoursOccurrenceRate,
    WeekdaysOccurrenceRate
)

SECONDS_IN_A_MINUTE = 60
SECONDS_IN_AN_HOUR = 60 * SECONDS_IN_A_MINUTE


class HoursRateTestCase(TestCase):

    def test_probability_at_interval_of_one_second_hourly(self):
        rate_for_morning_hours = HoursOccurrenceRate(
            start=0,
            end=12,
            hourly_rate=60
        )
        self.assertEqual(
            rate_for_morning_hours._probability_at_interval(1),
            1 / SECONDS_IN_A_MINUTE
        )

    def test_probability_at_interval_of_half_a_second_hourly(self):
        rate_for_morning_hours = HoursOccurrenceRate(
            start=0,
            end=12,
            hourly_rate=60
        )
        self.assertEqual(
            rate_for_morning_hours._probability_at_interval(0.5),
            1 / SECONDS_IN_A_MINUTE / 2
        )

    def test_probability_within_period_once_hourly(self):
        rate_for_morning_hours = HoursOccurrenceRate(
            start=0,
            end=12,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_morning_hours.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=1,
                    hour=6,
                    minute=0
                ),
                interval=1
            ),
            1 / SECONDS_IN_AN_HOUR
        )

    def test_probability_outside_period_once_hourly(self):
        rate_for_morning_hours = HoursOccurrenceRate(
            start=0,
            end=12,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_morning_hours.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=1,
                    hour=15,
                    minute=0
                ),
                interval=1
            ),
            0
        )


class WeekdaysOccurrenceRateTestCase(TestCase):

    def test_get_monday_last_midnight_start_of_month_and_year(self):
        rate_for_weekend_days = WeekdaysOccurrenceRate(
            start=5,
            end=6,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_weekend_days._get_last_monday_midnight(
                datetime(
                    year=1970,
                    month=1,
                    day=3,
                    hour=6,
                    minute=0
                )
            ),
            datetime(
                year=1969,
                month=12,
                day=29,
                hour=0,
                minute=0,
                second=0,
                microsecond=0
            )
        )

    def test_probability_within_period_saturday_once_hourly(self):
        rate_for_weekend_days = WeekdaysOccurrenceRate(
            start=5,
            end=6,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_weekend_days.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=3,
                    hour=6,
                    minute=0
                ),
                interval=1
            ),
            1 / SECONDS_IN_AN_HOUR
        )

    def test_probability_within_period_sunday_once_hourly(self):
        rate_for_weekend_days = WeekdaysOccurrenceRate(
            start=5,
            end=6,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_weekend_days.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=4,
                    hour=6,
                    minute=0
                ),
                interval=1
            ),
            1 / SECONDS_IN_AN_HOUR
        )

    def test_probability_outside_period_friday_once_hourly(self):
        rate_for_weekend_days = WeekdaysOccurrenceRate(
            start=5,
            end=6,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_weekend_days.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=2,
                    hour=0,
                    minute=0
                ),
                interval=1
            ),
            0
        )

    def test_probability_outside_period_monday_once_hourly(self):
        rate_for_weekend_days = WeekdaysOccurrenceRate(
            start=5,
            end=6,
            hourly_rate=1
        )
        self.assertEqual(
            rate_for_weekend_days.probability(
                time=datetime(
                    year=1970,
                    month=1,
                    day=5,
                    hour=0,
                    minute=0
                ),
                interval=1
            ),
            0
        )


class CalendarOccurrenceTestCase(TestCase):

    def test_initialised_with_no_occurrence_rates_exception(self):
        self.assertRaises(
            ValueError,
            CalendarOccurrenceRates
        )

    def test_occurs_with_only_hourly_probability_matching_period(self):

        hourly_calendar_probability = CalendarOccurrenceRates(
            hours=[
                HoursOccurrenceRate(
                    start=5,
                    end=6,
                    hourly_rate=1.0,
                ),
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertTrue(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )

    def test_occurs_with_only_weekday_probability_matching_period(self):
        hourly_calendar_probability = CalendarOccurrenceRates(
            weekdays=[
                WeekdaysOccurrenceRate(
                    start=3,
                    end=4,
                    hourly_rate=1.0
                )
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertTrue(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )

    def test_occurs_with_hour_and_weekday_probability_matching_period(self):
        hourly_calendar_probability = CalendarOccurrenceRates(
            hours=[
                HoursOccurrenceRate(
                    start=5,
                    end=6,
                    hourly_rate=1.0,
                ),
            ],
            weekdays=[
                WeekdaysOccurrenceRate(
                    start=3,
                    end=4,
                    hourly_rate=1.0
                )
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertTrue(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )

    def test_occurs_with_only_hourly_probability_non_matching_period(self):

        hourly_calendar_probability = CalendarOccurrenceRates(
            hours=[
                HoursOccurrenceRate(
                    start=10,
                    end=11,
                    hourly_rate=1.0,
                ),
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertFalse(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )

    def test_occurs_with_only_weekday_probability_non_matching_period(self):
        hourly_calendar_probability = CalendarOccurrenceRates(
            weekdays=[
                WeekdaysOccurrenceRate(
                    start=5,
                    end=6,
                    hourly_rate=1.0
                )
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertFalse(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )

    def test_occurs_with_hour_and_weekday_probability_non_matching_period(self):
        hourly_calendar_probability = CalendarOccurrenceRates(
            hours=[
                HoursOccurrenceRate(
                    start=10,
                    end=11,
                    hourly_rate=1.0,
                ),
            ],
            weekdays=[
                WeekdaysOccurrenceRate(
                    start=5,
                    end=6,
                    hourly_rate=1.0
                )
            ]
        )
        time_within_period = datetime(
            year=1970,
            month=1,
            day=1,
            hour=5,
            minute=30
        )
        self.assertFalse(
            hourly_calendar_probability.occurs(
                time=time_within_period,
                interval=SECONDS_IN_AN_HOUR,
            )
        )
