from datetime import date, datetime, time, timedelta
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.test import TestCase
from django.utils import timezone
from model_mommy import mommy

from .. models import (
    Action,
    Category,
    CustomAction,
    CustomGoal,
    Goal,
    Trigger,
    UserAction,
    UserCategory,
    UserCompletedAction,
    UserGoal,
)
from .. import user_feed


def tzdt(*args, **kwargs):
    """Return a timezone-aware datetime object."""
    tz = kwargs.pop("tz", timezone.utc)
    dt = datetime(*args)
    return timezone.make_aware(dt, timezone=tz)


class TestProgressStreaks(TestCase):

    def test__fil_streaks_with_exactly_seven(self):
        # Given exactly 7 values, we should get the same input as a result.
        data = [
            (date(2016, 8, 5), 4),
            (date(2016, 8, 6), 3),
            (date(2016, 8, 7), 4),
            (date(2016, 8, 8), 2),
            (date(2016, 8, 9), 6),
            (date(2016, 8, 10), 1),
            (date(2016, 8, 11), 7),
        ]
        with patch('utils.dateutils.timezone.now') as mock_now:
            mock_now.return_value = tzdt(2016, 8, 11, 9, 0)  # 9am
            self.assertEqual(list(user_feed._fill_streaks(data, days=7)), data)

    def test__fill_streaks_with_too_many(self):
        # Given 8 values, we want to keep 7. This should return the 7
        # most recent items in the list.
        data = [
            (date(2016, 8, 4), 21),
            (date(2016, 8, 5), 4),
            (date(2016, 8, 6), 3),
            (date(2016, 8, 7), 4),
            (date(2016, 8, 8), 2),
            (date(2016, 8, 9), 6),
            (date(2016, 8, 10), 1),
            (date(2016, 8, 11), 7),
        ]
        expected = [
            (date(2016, 8, 5), 4),
            (date(2016, 8, 6), 3),
            (date(2016, 8, 7), 4),
            (date(2016, 8, 8), 2),
            (date(2016, 8, 9), 6),
            (date(2016, 8, 10), 1),
            (date(2016, 8, 11), 7),
        ]
        with patch('utils.dateutils.timezone.now') as mock_now:
            mock_now.return_value = tzdt(2016, 8, 11, 9, 0)  # 9am
            self.assertEqual(list(user_feed._fill_streaks(data, days=7)), expected)

    def test__fill_streaks_with_too_few(self):
        # 4 values, we want 7
        data = [
            (date(2016, 8, 4), 21),
            (date(2016, 8, 7), 4),
            (date(2016, 8, 8), 2),
            (date(2016, 8, 11), 5),
        ]
        expected = [
            (date(2016, 8, 5), 0),
            (date(2016, 8, 6), 0),
            (date(2016, 8, 7), 4),
            (date(2016, 8, 8), 2),
            (date(2016, 8, 9), 0),
            (date(2016, 8, 10), 0),
            (date(2016, 8, 11), 5),
        ]
        with patch('utils.dateutils.timezone.now') as mock_now:
            mock_now.return_value = tzdt(2016, 8, 11, 9, 0)  # 9am
            self.assertEqual(list(user_feed._fill_streaks(data, days=7)), expected)


class TestUserProgress(TestCase):
    """This test case sets up data for the user, and then proceeds to verify
    their progress info. It focuses on the following functions:

    * user_feed.todays_actions_progress - The progress stats for today's
      scheduled actions.

    """

    @classmethod
    def setUpTestData(cls):
        cls.User = get_user_model()
        cls.user = cls.User.objects.create_user('user', 'u@example.com', 'secret')
        # NOTE: User's default timezone is America/Chicago

        # Create a Category/Goal/Action(s) content
        cls.category = mommy.make(Category, title="CAT", state="published")
        cls.goal = mommy.make(Goal, title="GTITLE", state='published')
        cls.goal.categories.add(cls.category)
        cls.action1 = mommy.make(Action, title='A1', state='published')
        cls.action1.goals.add(cls.goal)
        cls.action2 = mommy.make(Action, title='A2', state='published')
        cls.action2.goals.add(cls.goal)
        cls.action3 = mommy.make(Action, title='A3', state='published')
        cls.action3.goals.add(cls.goal)

        # Assign the user the above content
        cls.uc = mommy.make(UserCategory, user=cls.user, category=cls.category)
        cls.ug = mommy.make(UserGoal, user=cls.user, goal=cls.goal)

        dt = timezone.now()
        cls.dt = tzdt(dt.year, dt.month, dt.day, 9, 0)  # Today, 9am
        with patch('goals.models.triggers.timezone.now') as mock_now:
            # Current time: 9am
            mock_now.return_value = cls.dt

            # Custom Triggers for each UserAction
            cls.t1 = Trigger.objects.create(
                user=cls.user,
                name="a1-trigger",
                time=time(22, 0),
                recurrences="RRULE:FREQ=DAILY"
            )
            cls.t2 = Trigger.objects.create(
                user=cls.user,
                name="a2-trigger",
                time=time(22, 30),
                recurrences="RRULE:FREQ=DAILY"
            )
            cls.t3 = Trigger.objects.create(
                user=cls.user,
                name="a3-trigger",
                time=time(23, 0),
                recurrences="RRULE:FREQ=DAILY"
            )

            # Create the UserActions
            cls.ua1 = UserAction.objects.create(
                user=cls.user,
                action=cls.action1,
                custom_trigger=cls.t1
            )
            cls.ua2 = UserAction.objects.create(
                user=cls.user,
                action=cls.action2,
                custom_trigger=cls.t2
            )
            cls.ua3 = UserAction.objects.create(
                user=cls.user,
                action=cls.action3,
                custom_trigger=cls.t3
            )

    @patch('sys.stderr')
    @patch('sys.stdout')
    def _run_refresh_useractions(self, mock_stderr, mock_stdout, dt=None):
        """Calls the `refresh_useractions` management command`."""
        timezone_path = 'goals.managers.timezone'
        log_path = "goals.management.commands.refresh_useractions.logger"
        with patch(log_path):
            with patch(timezone_path) as mock_timezone:
                if dt is None:
                    dt = self.dt  # today, 9am
                mock_timezone.now.return_value = dt
                call_command('refresh_useractions')

    def test_todays_actions_progress(self):
        with patch('django.utils.timezone.now') as mock_now: