from __future__ import print_function

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

from datetime import datetime
import functools
import logging
import os
import time
import unittest

from freezegun import freeze_time


class FakeTimeIOLoop(object):

    def __init__(self, freezer):
        self._offset = 0
        self._freezer = freezer

    def time(self):
        return time.time() + self._offset

    def add_timeout(self, timeout, callback):
        self._callback = (timeout, callback)

    def call_later(self, timeout, callback):
        self._max = timeout

    def close(self):
        pass

    def start(self):
        for _ in range(self._max):
            _timeout, _callback = self._callback
            self._freezer.stop()
            self._freezer.time_to_freeze = datetime.utcfromtimestamp(_timeout)
            self._freezer.start()
            _callback()
            self._offset = _timeout - time.time()

    def stop(self):
        pass


class TestCronTabCallback(unittest.TestCase):

    BEGIN_TIME = "2015-11-08T00:00:00Z"

    def __init__(self, *args, **kwargs):
        super(TestCronTabCallback, self).__init__(*args, **kwargs)

    def setUp(self):
        unittest.TestCase.setUp(self)

        self._freezer = freeze_time(self.BEGIN_TIME)
        self._freezer.start()
        self.io_loop = FakeTimeIOLoop(self._freezer)
        self.calls = []

    def tearDown(self):
        unittest.TestCase.tearDown(self)
        self._freezer.stop()

    def crontab_task(self):
        self.calls.append(datetime.utcfromtimestamp(
            self.io_loop.time()).strftime("%Y-%m-%dT%H:%M:%S"))

    def _target(self, schedule):

        from tornado_crontab import CronTabCallback

        pc = CronTabCallback(self.crontab_task, schedule, self.io_loop)
        pc.start()

    def _test(self, schedule, asserts):

        self._target(schedule)
        self.io_loop.call_later(10, self.io_loop.stop)
        self.io_loop.start()

        self.assertEqual(self.calls, asserts)

    def test_every_minute(self):

        self._test("* * * * *", ["2015-11-08T00:01:00", "2015-11-08T00:02:00",
                                 "2015-11-08T00:03:00", "2015-11-08T00:04:00",
                                 "2015-11-08T00:05:00", "2015-11-08T00:06:00",
                                 "2015-11-08T00:07:00", "2015-11-08T00:08:00",
                                 "2015-11-08T00:09:00", "2015-11-08T00:10:00"])

    def test_every_hour(self):

        self._test("0 * * * *", ["2015-11-08T01:00:00", "2015-11-08T02:00:00",
                                 "2015-11-08T03:00:00", "2015-11-08T04:00:00",
                                 "2015-11-08T05:00:00", "2015-11-08T06:00:00",
                                 "2015-11-08T07:00:00", "2015-11-08T08:00:00",
                                 "2015-11-08T09:00:00", "2015-11-08T10:00:00"])

    def test_every_day(self):

        self._test("0 0 * * *", ["2015-11-09T00:00:00", "2015-11-10T00:00:00",
                                 "2015-11-11T00:00:00", "2015-11-12T00:00:00",
                                 "2015-11-13T00:00:00", "2015-11-14T00:00:00",
                                 "2015-11-15T00:00:00", "2015-11-16T00:00:00",
                                 "2015-11-17T00:00:00", "2015-11-18T00:00:00"])

    def test_every_monday(self):

        self._test("0 0 * * 1", ["2015-11-09T00:00:00", "2015-11-16T00:00:00",
                                 "2015-11-23T00:00:00", "2015-11-30T00:00:00",
                                 "2015-12-07T00:00:00", "2015-12-14T00:00:00",
                                 "2015-12-21T00:00:00", "2015-12-28T00:00:00",
                                 "2016-01-04T00:00:00", "2016-01-11T00:00:00"])

    def test_every_month(self):

        self._test("0 0 1 * *", ["2015-12-01T00:00:00", "2016-01-01T00:00:00",
                                 "2016-02-01T00:00:00", "2016-03-01T00:00:00",
                                 "2016-04-01T00:00:00", "2016-05-01T00:00:00",
                                 "2016-06-01T00:00:00", "2016-07-01T00:00:00",
                                 "2016-08-01T00:00:00", "2016-09-01T00:00:00"])

    def test_every_year(self):

        self._test("0 0 1 1 *", ["2016-01-01T00:00:00", "2017-01-01T00:00:00",
                                 "2018-01-01T00:00:00", "2019-01-01T00:00:00",
                                 "2020-01-01T00:00:00", "2021-01-01T00:00:00",
                                 "2022-01-01T00:00:00", "2023-01-01T00:00:00",
                                 "2024-01-01T00:00:00", "2025-01-01T00:00:00"])


class TestCrontabDecorator(TestCronTabCallback):

    def __init__(self, *args, **kwargs):
        super(TestCrontabDecorator, self).__init__(*args, **kwargs)

    def _target(self, schedule):

        from tornado_crontab import crontab

        @crontab(schedule, self.io_loop)
        def decorate_task():
            self.crontab_task()

        decorate_task()


class TestCrontabLogging(unittest.TestCase):

    def test__get_func_spec_no_args(self):

        from tornado_crontab import CronTabCallback

        def _func():
            pass

        _crontab = CronTabCallback(_func, "* * * * *")
        self.assertEqual((_func, [], {}), _crontab._get_func_spec())

    def test__get_func_spec_args(self):

        from tornado_crontab import CronTabCallback

        def _func(arg1, arg2):
            pass

        _args = ["value1", "value2"]
        _crontab = CronTabCallback(
            functools.partial(_func, *_args), "* * * * *")
        self.assertEqual((_func, _args, {}), _crontab._get_func_spec())

        _crontab = CronTabCallback(
            functools.partial(
                functools.partial(_func, _args[0]), _args[1]), "* * * * *")
        self.assertEqual((_func, _args, {}), _crontab._get_func_spec())

    def test__get_func_spec_kwargs(self):

        from tornado_crontab import CronTabCallback

        def _func(arg1, arg2):
            pass

        _kwargs = {"arg1": "value1", "arg2": "value2"}
        _crontab = CronTabCallback(
            functools.partial(_func, **_kwargs), "* * * * *")
        self.assertEqual((_func, [], _kwargs), _crontab._get_func_spec())

        _crontab = CronTabCallback(
            functools.partial(
                functools.partial(_func, arg1="value1"), arg2="value2"),
            "* * * * *")
        self.assertEqual((_func, [], _kwargs), _crontab._get_func_spec())

    def test__logging(self):

        from tornado_crontab import CronTabCallback
        from tornado_crontab._crontab import log_crontab

        def _func(arg1, arg2):
            pass

        _crontab = CronTabCallback(
            functools.partial(_func, "value1", arg2="value2"), "* * * * *")
        _crontab._running = True

        _stream = StringIO()
        log_crontab.addHandler(logging.StreamHandler(_stream))
        _crontab._logging(logging.DEBUG)
        _log = _stream.getvalue()

        # for windows
        if os.name == "nt":
            user = os.environ.get("USERNAME")

        # for other os
        else:
            import pwd
            user = pwd.getpwuid(os.geteuid()).pw_name

        self.assertEqual(
            " ".join(["tornado-crontab[%d]:" % os.getpid(), "(%s)" % user,
                      "FUNC (_func ['value1'] {'arg2': 'value2'})\n"]), _log)


if __name__ == "__main__":
    unittest.main()