import unittest
from datetime import datetime, timedelta
from analogai.foundation.common.enums.deontic_modality import DeonticModality
from analogai.foundation.common.utils.statement_serializers import StatementSerializer, serialize_statement
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.time import Time


class TestStatementSerializer(unittest.TestCase):
    
    def test_serialize_basic(self):
        serializer = StatementSerializer.create_default()
        result = serializer.serialize("Human", "is", "mortal", 0.8)
        self.assertIn("Human", result)
        self.assertIn("mortal", result)
    
    def test_serialize_negative_probability(self):
        serializer = StatementSerializer.create_default()
        result = serializer.serialize("Human", "is", "mortal", 0.2)
        self.assertIn("not", result.lower())
    
    def test_serialize_uncertain_probability(self):
        serializer = StatementSerializer.create_default()
        result = serializer.serialize("Human", "is", "mortal", 0.5)
        self.assertIn("might", result.lower())
    
    def test_serialize_statement_with_modifier(self):
        today = datetime.now()
        modifier = Modifier(
            location="Rome",
            from_time=Time(year=today.year, month=today.month, day=today.day)
        )
        result = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier
        )
        self.assertIn("human", result.lower())
        self.assertIn("mortal", result.lower())
        self.assertIn("rome", result.lower())
    
    def test_serialize_statement_with_timezone(self):
        today = datetime.now()
        modifier = Modifier(
            location="New York",
            from_time=Time(year=today.year, month=today.month, day=today.day)
        )
        result = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="America/New_York"
        )
        self.assertIn("human", result.lower())
        self.assertIn("mortal", result.lower())
        self.assertIn("new york", result.lower())
    
    def test_serialize_statement_with_different_timezones(self):
        today = datetime.now()
        modifier = Modifier(
            from_time=Time(year=today.year, month=today.month, day=today.day)
        )
        
        result_ny = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="America/New_York"
        )
        
        result_london = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="Europe/London"
        )
        
        result_tokyo = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="Asia/Tokyo"
        )
        
        self.assertIn("human", result_ny.lower())
        self.assertIn("mortal", result_ny.lower())
        self.assertIn("human", result_london.lower())
        self.assertIn("mortal", result_london.lower())
        self.assertIn("human", result_tokyo.lower())
        self.assertIn("mortal", result_tokyo.lower())
    
    def test_serialize_statement_with_timezone_and_time(self):
        from zoneinfo import ZoneInfo
        utc_tz = ZoneInfo("UTC")
        utc_now = datetime.now(utc_tz)
        yesterday = utc_now - timedelta(days=1)
        modifier = Modifier(
            from_time=Time(year=yesterday.year, month=yesterday.month, day=yesterday.day)
        )
        result = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="UTC"
        )
        self.assertIn("human", result.lower())
        self.assertIn("mortal", result.lower())
        self.assertIn("yesterday", result.lower())
    
    def test_serialize_statement_with_timezone_and_future_time(self):
        from zoneinfo import ZoneInfo
        london_tz = ZoneInfo("Europe/London")
        london_now = datetime.now(london_tz)
        tomorrow = london_now + timedelta(days=1)
        modifier = Modifier(
            from_time=Time(year=tomorrow.year, month=tomorrow.month, day=tomorrow.day)
        )
        result = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="Europe/London"
        )
        self.assertIn("human", result.lower())
        self.assertIn("mortal", result.lower())
        self.assertIn("tomorrow", result.lower())
    
    def test_serialize_statement_without_timezone(self):
        today = datetime.now()
        modifier = Modifier(
            from_time=Time(year=today.year, month=today.month, day=today.day)
        )
        result = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier
        )
        self.assertIn("human", result.lower())
        self.assertIn("mortal", result.lower())
    
    def test_serialize_statement_timezone_produces_different_results(self):
        from datetime import datetime, timedelta
        from zoneinfo import ZoneInfo
        
        now_ny = datetime.now(ZoneInfo("America/New_York"))
        fixed_time_ny = now_ny - timedelta(hours=3)
        fixed_time = Time(year=fixed_time_ny.year, month=fixed_time_ny.month, day=fixed_time_ny.day, hour=fixed_time_ny.hour, minute=30)
        modifier = Modifier(from_time=fixed_time)
        
        result_ny = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="America/New_York"
        )
        
        result_london = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="Europe/London"
        )
        
        result_ny_again = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier,
            timezone="America/New_York"
        )
        
        result_no_timezone = serialize_statement(
            "Human",
            "mortal",
            Relation.from_string("is"),
            0.8,
            modifier
        )
        
        self.assertIsNotNone(result_ny)
        self.assertIsNotNone(result_london)
        self.assertEqual(result_ny, result_ny_again, "Same timezone should produce same results")
        ny_london_differ = result_ny != result_london
        ny_no_tz_differ = result_ny != result_no_timezone
        time_terms_overlap = any(t in result_ny.lower() for t in ["today", "yesterday"])
        self.assertTrue(ny_london_differ or time_terms_overlap, "NY and London should differ unless both produce today/yesterday")
        self.assertTrue(ny_no_tz_differ or time_terms_overlap, "With timezone should differ from without unless result is today/yesterday")
        self.assertIn("human", result_ny.lower())
        self.assertIn("mortal", result_ny.lower())
        self.assertIn("human", result_london.lower())
        self.assertIn("mortal", result_london.lower())
        
        result_ny_lower = result_ny.lower()
        result_london_lower = result_london.lower()
        time_terms = ["ago", "hour", "minute", "now", "today", "yesterday", "tomorrow", "day", "week", "month", "year", "last", "next", "in"]
        self.assertTrue(
            any(term in result_ny_lower for term in time_terms),
            f"NY result '{result_ny}' should contain time-related terms"
        )
        self.assertTrue(
            any(term in result_london_lower for term in time_terms),
            f"London result '{result_london}' should contain time-related terms"
        )
    
    def test_serialize_statement_timezone_with_hours_produces_different_results(self):
        from zoneinfo import ZoneInfo
        from unittest.mock import patch

        fixed_date = datetime(2024, 6, 15, 12, 30, 0, tzinfo=ZoneInfo("UTC"))
        reference_ny = datetime(2024, 6, 15, 10, 0, 0, tzinfo=ZoneInfo("America/New_York"))
        fixed_time = Time(
            year=fixed_date.year,
            month=fixed_date.month,
            day=fixed_date.day,
            hour=fixed_date.hour,
            minute=fixed_date.minute
        )
        modifier = Modifier(from_time=fixed_time)

        def _now(tz=None):
            if tz and "New_York" in str(tz):
                return reference_ny
            if tz and "London" in str(tz):
                return reference_ny.astimezone(ZoneInfo("Europe/London"))
            return reference_ny.astimezone(tz) if tz else reference_ny

        import analogai.foundation.common.utils.time_utils.timezone_utils as tz_utils
        with patch.object(tz_utils, "datetime") as mock_dt:
            mock_dt.now.side_effect = _now

            result_ny = serialize_statement(
                "Human",
                "mortal",
                Relation.from_string("is"),
                0.8,
                modifier,
                timezone="America/New_York"
            )

            result_london = serialize_statement(
                "Human",
                "mortal",
                Relation.from_string("is"),
                0.8,
                modifier,
                timezone="Europe/London"
            )

        self.assertNotEqual(
            result_ny,
            result_london,
            "NY and London must produce different results when hour/minute specified"
        )
        self.assertIn("hour", result_ny.lower())
        self.assertIn("hour", result_london.lower())
    
    def test_serialize_statement_with_obligatory_deontic_modality(self):
        modifier = Modifier(deontic_modality=DeonticModality.OBLIGATORY)
        result = serialize_statement(
            "aristotle",
            "iphone",
            Relation.from_string("do"),
            0.8,
            modifier
        )
        self.assertIn("aristotle", result.lower())
        self.assertIn("iphone", result.lower())
        self.assertIn("must", result.lower())
        self.assertNotIn("it is obligatory", result.lower())
    
    def test_serialize_statement_with_permitted_deontic_modality(self):
        modifier = Modifier(deontic_modality=DeonticModality.PERMITTED)
        result = serialize_statement(
            "aristotle",
            "iphone",
            Relation.from_string("do"),
            0.8,
            modifier
        )
        self.assertIn("aristotle", result.lower())
        self.assertIn("iphone", result.lower())
        self.assertIn("can", result.lower())
    
    def test_serialize_statement_with_forbidden_deontic_modality(self):
        modifier = Modifier(deontic_modality=DeonticModality.FORBIDDEN)
        result = serialize_statement(
            "aristotle",
            "iphone",
            Relation.from_string("do"),
            0.8,
            modifier
        )
        self.assertIn("aristotle", result.lower())
        self.assertIn("iphone", result.lower())
        self.assertIn("can not", result.lower())


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