import unittest
from datetime import datetime, timedelta

from analogai.foundation.core.value_objects.time import Time

from analogai.deepthink.presentation.parsing.time_parser import TimeParser


class TestTimeParser(unittest.TestCase):
    def test_parse_now(self):
        result = TimeParser.parse("now")
        self.assertIsNotNone(result)
        now = datetime.now()
        self.assertEqual(result.year, now.year)
        self.assertEqual(result.month, now.month)
        self.assertEqual(result.day, now.day)
        self.assertIsNotNone(result.hour)
        self.assertIsNone(result.minute, "Parsing 'now' should not include minutes, only the current hour")

    def test_parse_today(self):
        result = TimeParser.parse("today")
        self.assertIsNotNone(result)
        today = datetime.now()
        self.assertEqual(result.year, today.year)
        self.assertEqual(result.month, today.month)
        self.assertEqual(result.day, today.day)

    def test_parse_tomorrow(self):
        result = TimeParser.parse("tomorrow")
        self.assertIsNotNone(result)
        tomorrow = datetime.now() + timedelta(days=1)
        self.assertEqual(result.year, tomorrow.year)
        self.assertEqual(result.month, tomorrow.month)
        self.assertEqual(result.day, tomorrow.day)

    def test_parse_yesterday(self):
        result = TimeParser.parse("yesterday")
        self.assertIsNotNone(result)
        yesterday = datetime.now() - timedelta(days=1)
        self.assertEqual(result.year, yesterday.year)
        self.assertEqual(result.month, yesterday.month)
        self.assertEqual(result.day, yesterday.day)

    def test_parse_weekday(self):
        result = TimeParser.parse("saturday")
        self.assertIsNotNone(result)
        now = datetime.now()
        target = 5
        delta = (target - now.weekday()) % 7
        expected = now + timedelta(days=delta)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_weekday_with_time(self):
        result = TimeParser.parse("saturday 14:30")
        self.assertIsNotNone(result)
        self.assertEqual(result.hour, 14)
        self.assertEqual(result.minute, 30)

    def test_parse_relative_time_minutes_ago(self):
        result = TimeParser.parse("30 minutes ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(minutes=30)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_hours_ago(self):
        result = TimeParser.parse("2 hours ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(hours=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_days_ago(self):
        result = TimeParser.parse("5 days ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(days=5)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_weeks_ago(self):
        result = TimeParser.parse("2 weeks ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(weeks=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)

    def test_parse_relative_time_months_ago(self):
        result = TimeParser.parse("3 months ago")
        self.assertIsNotNone(result)
        self.assertIsNotNone(result.year)
        self.assertIsNotNone(result.month)

    def test_parse_relative_time_years_ago(self):
        result = TimeParser.parse("1 year ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(days=365)
        self.assertEqual(result.year, expected.year)

    def test_parse_relative_time_in_minutes(self):
        result = TimeParser.parse("in 30 minutes")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(minutes=30)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_hours(self):
        result = TimeParser.parse("in 2 hours")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(hours=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_days(self):
        result = TimeParser.parse("in 5 days")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(days=5)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_one_hour(self):
        result = TimeParser.parse("in one hour")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(hours=1)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_an_hour(self):
        result = TimeParser.parse("in an hour")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(hours=1)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_a_minute(self):
        result = TimeParser.parse("in a minute")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(minutes=1)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_one_minute(self):
        result = TimeParser.parse("in one minute")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(minutes=1)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_two_hours_ago(self):
        result = TimeParser.parse("two hours ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(hours=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_three_days_ago(self):
        result = TimeParser.parse("three days ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(days=3)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_four_weeks_ago(self):
        result = TimeParser.parse("four weeks ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(weeks=4)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)

    def test_parse_relative_time_five_minutes_ago(self):
        result = TimeParser.parse("five minutes ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(minutes=5)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_two_hours(self):
        result = TimeParser.parse("in two hours")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(hours=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_three_days(self):
        result = TimeParser.parse("in three days")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(days=3)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_four_weeks(self):
        result = TimeParser.parse("in four weeks")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(weeks=4)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)

    def test_parse_relative_time_in_twenty_one_days(self):
        result = TimeParser.parse("in twenty-one days")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(days=21)
        self.assertEqual(result.year, expected.year)
        result_date = datetime(result.year, result.month, result.day).date()
        expected_date = expected.date()
        self.assertEqual(result_date, expected_date)

    def test_parse_relative_time_thirty_five_minutes_ago(self):
        result = TimeParser.parse("thirty-five minutes ago")
        self.assertIsNotNone(result)
        expected = datetime.now() - timedelta(minutes=35)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)
        self.assertEqual(result.day, expected.day)

    def test_parse_relative_time_in_weeks(self):
        result = TimeParser.parse("in 2 weeks")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(weeks=2)
        self.assertEqual(result.year, expected.year)
        self.assertEqual(result.month, expected.month)

    def test_parse_relative_time_in_months(self):
        result = TimeParser.parse("in 3 months")
        self.assertIsNotNone(result)
        self.assertIsNotNone(result.year)
        self.assertIsNotNone(result.month)

    def test_parse_relative_time_in_years(self):
        result = TimeParser.parse("in 1 year")
        self.assertIsNotNone(result)
        expected = datetime.now() + timedelta(days=365)
        self.assertEqual(result.year, expected.year)

    def test_parse_relative_time_one_minute_ago(self):
        result = TimeParser.parse("one minute ago")
        self.assertIsNotNone(result)

    def test_parse_relative_time_an_hour_ago(self):
        result = TimeParser.parse("an hour ago")
        self.assertIsNotNone(result)

    def test_parse_relative_time_a_day_ago(self):
        result = TimeParser.parse("a day ago")
        self.assertIsNotNone(result)

    def test_parse_time_with_colon(self):
        result = TimeParser.parse("14:30")
        self.assertIsNotNone(result)
        today = datetime.now()
        self.assertEqual(result.year, today.year)
        self.assertEqual(result.month, today.month)
        self.assertEqual(result.day, today.day)
        self.assertEqual(result.hour, 14)
        self.assertEqual(result.minute, 30)

    def test_parse_today_with_time(self):
        result = TimeParser.parse("today 14:30")
        self.assertIsNotNone(result)
        today = datetime.now()
        self.assertEqual(result.year, today.year)
        self.assertEqual(result.month, today.month)
        self.assertEqual(result.day, today.day)
        self.assertEqual(result.hour, 14)
        self.assertEqual(result.minute, 30)

    def test_parse_tomorrow_with_time(self):
        result = TimeParser.parse("tomorrow 10:00")
        self.assertIsNotNone(result)
        tomorrow = datetime.now() + timedelta(days=1)
        self.assertEqual(result.year, tomorrow.year)
        self.assertEqual(result.month, tomorrow.month)
        self.assertEqual(result.day, tomorrow.day)
        self.assertEqual(result.hour, 10)
        self.assertEqual(result.minute, 0)

    def test_parse_yesterday_with_time(self):
        result = TimeParser.parse("yesterday 18:45")
        self.assertIsNotNone(result)
        yesterday = datetime.now() - timedelta(days=1)
        self.assertEqual(result.year, yesterday.year)
        self.assertEqual(result.month, yesterday.month)
        self.assertEqual(result.day, yesterday.day)
        self.assertEqual(result.hour, 18)
        self.assertEqual(result.minute, 45)

    def test_parse_date_with_dashes(self):
        result = TimeParser.parse("2024-03-15")
        self.assertIsNotNone(result)
        self.assertEqual(result.year, 2024)
        self.assertEqual(result.month, 3)
        self.assertEqual(result.day, 15)

    def test_parse_date_with_month_name(self):
        result = TimeParser.parse("28 January 1864")
        self.assertIsNotNone(result)
        self.assertEqual(result.year, 1864)
        self.assertEqual(result.month, 1)
        self.assertEqual(result.day, 28)

    def test_parse_year_only(self):
        result = TimeParser.parse("1990")
        self.assertIsNotNone(result)
        self.assertEqual(result.year, 1990)

    def test_parse_invalid_string(self):
        result = TimeParser.parse("invalid time string")
        self.assertIsNone(result)

    def test_parse_empty_string(self):
        result = TimeParser.parse("")
        self.assertIsNone(result)

    def test_parse_none(self):
        result = TimeParser.parse(None)
        self.assertIsNone(result)

    def test_parse_day_range(self):
        from_time, to_time = TimeParser.parse_day_range("today")
        self.assertIsNotNone(from_time)
        self.assertIsNotNone(to_time)
        today = datetime.now()
        self.assertEqual(from_time.year, today.year)
        self.assertEqual(from_time.month, today.month)
        self.assertEqual(from_time.day, today.day)
        self.assertEqual(to_time.year, today.year)
        self.assertEqual(to_time.month, today.month)
        self.assertEqual(to_time.day, today.day)

    def test_parse_day_range_tomorrow(self):
        from_time, to_time = TimeParser.parse_day_range("tomorrow")
        self.assertIsNotNone(from_time)
        self.assertIsNotNone(to_time)
        tomorrow = datetime.now() + timedelta(days=1)
        self.assertEqual(from_time.year, tomorrow.year)
        self.assertEqual(from_time.month, tomorrow.month)
        self.assertEqual(from_time.day, tomorrow.day)


class TestTimeZoneSupport(unittest.TestCase):
    def test_parser_with_timezone(self):
        time_str = "now"
        parsed = TimeParser.parse(time_str, timezone="America/New_York")
        self.assertIsNotNone(parsed)
        self.assertIsNotNone(parsed.year)
        self.assertIsNotNone(parsed.month)
        self.assertIsNotNone(parsed.day)

    def test_parser_today_with_timezone(self):
        time_str = "today"
        parsed = TimeParser.parse(time_str, timezone="Europe/London")
        self.assertIsNotNone(parsed)
        self.assertIsNotNone(parsed.year)
        self.assertIsNotNone(parsed.month)
        self.assertIsNotNone(parsed.day)

    def test_parser_tomorrow_with_timezone(self):
        time_str = "tomorrow"
        parsed = TimeParser.parse(time_str, timezone="Asia/Tokyo")
        self.assertIsNotNone(parsed)
        self.assertIsNotNone(parsed.year)
        self.assertIsNotNone(parsed.month)
        self.assertIsNotNone(parsed.day)

    def test_parser_relative_time_with_timezone(self):
        time_str = "2 hours ago"
        parsed = TimeParser.parse(time_str, timezone="UTC")
        self.assertIsNotNone(parsed)
        self.assertIsNotNone(parsed.year)
        self.assertIsNotNone(parsed.month)
        self.assertIsNotNone(parsed.day)

    def test_parser_parse_day_range_with_timezone(self):
        time_str = "today"
        from_time, to_time = TimeParser.parse_day_range(time_str, timezone="Europe/London")
        self.assertIsNotNone(from_time)
        self.assertIsNotNone(to_time)
        self.assertEqual(from_time.year, to_time.year)
        self.assertEqual(from_time.month, to_time.month)
        self.assertEqual(from_time.day, to_time.day)
        self.assertEqual(from_time.hour, 0)
        self.assertEqual(from_time.minute, 0)
        self.assertEqual(to_time.hour, 23)
        self.assertEqual(to_time.minute, 59)

    def test_timezone_invalid_raises_error(self):
        from zoneinfo._common import ZoneInfoNotFoundError

        with self.assertRaises(ZoneInfoNotFoundError):
            TimeParser.parse("now", timezone="Invalid/Timezone")

    def test_parser_now_different_timezones(self):
        from zoneinfo import ZoneInfo

        ny_now = TimeParser.parse("now", timezone="America/New_York")
        london_now = TimeParser.parse("now", timezone="Europe/London")

        self.assertIsNotNone(ny_now)
        self.assertIsNotNone(london_now)
        self.assertIsNotNone(ny_now.hour)
        self.assertIsNone(ny_now.minute, "Parsing 'now' should not include minutes, only the current hour")
        self.assertIsNotNone(london_now.hour)
        self.assertIsNone(london_now.minute, "Parsing 'now' should not include minutes, only the current hour")

        ny_dt = datetime.now(ZoneInfo("America/New_York"))
        london_dt = datetime.now(ZoneInfo("Europe/London"))

        self.assertEqual(ny_now.year, ny_dt.year)
        self.assertEqual(ny_now.month, ny_dt.month)
        self.assertEqual(ny_now.day, ny_dt.day)
        self.assertEqual(ny_now.hour, ny_dt.hour)
        self.assertEqual(london_now.year, london_dt.year)
        self.assertEqual(london_now.month, london_dt.month)
        self.assertEqual(london_now.day, london_dt.day)
        self.assertEqual(london_now.hour, london_dt.hour)

    def test_parser_relative_time_different_timezones(self):
        from zoneinfo import ZoneInfo

        ny_result = TimeParser.parse("in one hour", timezone="America/New_York")
        london_result = TimeParser.parse("in one hour", timezone="Europe/London")

        self.assertIsNotNone(ny_result)
        self.assertIsNotNone(london_result)

        ny_now = datetime.now(ZoneInfo("America/New_York"))
        london_now = datetime.now(ZoneInfo("Europe/London"))

        ny_expected = ny_now + timedelta(hours=1)
        london_expected = london_now + timedelta(hours=1)

        self.assertEqual(ny_result.year, ny_expected.year)
        self.assertEqual(ny_result.month, ny_expected.month)
        self.assertEqual(ny_result.day, ny_expected.day)
        self.assertEqual(london_result.year, london_expected.year)
        self.assertEqual(london_result.month, london_expected.month)
        self.assertEqual(london_result.day, london_expected.day)

    def test_parser_today_different_timezones(self):
        from zoneinfo import ZoneInfo

        ny_today = TimeParser.parse("today", timezone="America/New_York")
        london_today = TimeParser.parse("today", timezone="Europe/London")

        self.assertIsNotNone(ny_today)
        self.assertIsNotNone(london_today)

        ny_now = datetime.now(ZoneInfo("America/New_York"))
        london_now = datetime.now(ZoneInfo("Europe/London"))

        self.assertEqual(ny_today.year, ny_now.year)
        self.assertEqual(ny_today.month, ny_now.month)
        self.assertEqual(ny_today.day, ny_now.day)
        self.assertEqual(london_today.year, london_now.year)
        self.assertEqual(london_today.month, london_now.month)
        self.assertEqual(london_today.day, london_now.day)

    def test_parser_tomorrow_different_timezones(self):
        from zoneinfo import ZoneInfo

        ny_tomorrow = TimeParser.parse("tomorrow", timezone="America/New_York")
        london_tomorrow = TimeParser.parse("tomorrow", timezone="Europe/London")

        self.assertIsNotNone(ny_tomorrow)
        self.assertIsNotNone(london_tomorrow)

        ny_now = datetime.now(ZoneInfo("America/New_York"))
        london_now = datetime.now(ZoneInfo("Europe/London"))

        ny_expected = ny_now + timedelta(days=1)
        london_expected = london_now + timedelta(days=1)

        self.assertEqual(ny_tomorrow.year, ny_expected.year)
        self.assertEqual(ny_tomorrow.month, ny_expected.month)
        self.assertEqual(ny_tomorrow.day, ny_expected.day)
        self.assertEqual(london_tomorrow.year, london_expected.year)
        self.assertEqual(london_tomorrow.month, london_expected.month)
        self.assertEqual(london_tomorrow.day, london_expected.day)

    def test_parser_yesterday_different_timezones(self):
        from zoneinfo import ZoneInfo

        ny_yesterday = TimeParser.parse("yesterday", timezone="America/New_York")
        london_yesterday = TimeParser.parse("yesterday", timezone="Europe/London")

        self.assertIsNotNone(ny_yesterday)
        self.assertIsNotNone(london_yesterday)

        ny_now = datetime.now(ZoneInfo("America/New_York"))
        london_now = datetime.now(ZoneInfo("Europe/London"))

        ny_expected = ny_now - timedelta(days=1)
        london_expected = london_now - timedelta(days=1)

        self.assertEqual(ny_yesterday.year, ny_expected.year)
        self.assertEqual(ny_yesterday.month, ny_expected.month)
        self.assertEqual(ny_yesterday.day, ny_expected.day)
        self.assertEqual(london_yesterday.year, london_expected.year)
        self.assertEqual(london_yesterday.month, london_expected.month)
        self.assertEqual(london_yesterday.day, london_expected.day)


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