import unittest

from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.modules.categorical_deduction.categorical_deduction import CategoricalDeduction
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.modules.notion.notion import Notion
from analogai.foundation.tests.functional.services.setup import ServicesTestConfig


class TestCategoricalDeductionServiceFunctional(unittest.TestCase):
    def setUp(self):
        self.config = ServicesTestConfig()
        self.user = self.config.user
        self.agent = self.config.agent
        self.belief_service = self.config.belief_service
        self.statement_service = self.config.statement_service
        self.categorical_deduction_service = self.config.categorical_deduction_service
        self.notion_repository = self.config.notion_repository

    def tearDown(self):
        self.config.tear_down()

    def _create_notion(self, caption: str, notion_id: str) -> Notion:
        notion = Notion(user_id=self.user.id, agent_id=self.agent.id, caption=caption, id=notion_id)
        self.notion_repository.create(notion)
        return notion

    def _create_statement(self, subject: Notion, complement_notion: Notion, relation: Relation, modifier: Modifier | None = None):
        return self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=subject,
            complement_notion=complement_notion,
            relation=relation,
            probability=1.0,
            validity=1.0,
            modifier=modifier,
        )

    def _deductions_from_belief(self, belief):
        return [stmt for stmt in belief.statements if isinstance(stmt, CategoricalDeduction)]

    def test_live_categorical_deduction_builds_belief(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        self._create_statement(socrates, human, Relation.from_string("is"))
        self._create_statement(human, mortal, Relation.from_string("is"))

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, None, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=None,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 1)

        major_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=human.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        )
        minor_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=human.id,
            relation=Relation.from_string("is"),
            modifier=None,
        )
        major = beliefs_by_key.get(major_key)
        minor = beliefs_by_key.get(minor_key)
        self.assertIsNotNone(major)
        self.assertIsNotNone(minor)
        if major is None or minor is None:
            self.fail("Expected major and minor premises to be available")
        self.assertEqual(deductions[0].major_premise, major)
        self.assertEqual(deductions[0].minor_premise, minor)

    def test_find_deductions_by_agent_id(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        self._create_statement(socrates, human, Relation.from_string("is"))
        self._create_statement(human, mortal, Relation.from_string("is"))

        deductions = self.categorical_deduction_service.find_deductions_by_agent_id(
            agent_id=self.agent.id,
            max_depth=5,
            limit=10,
            offset=0,
        )
        target_relation = Relation.from_string("is")
        matching = [
            deduction
            for deduction in deductions
            if isinstance(deduction, CategoricalDeduction)
            and deduction.subject_notion.id == socrates.id
            and deduction.complement_notion.id == mortal.id
            and deduction.relation == target_relation
        ]
        self.assertEqual(len(matching), 1)
        deduction = matching[0]
        self.assertIsNotNone(deduction.major_premise)
        self.assertIsNotNone(deduction.minor_premise)
        self.assertEqual(deduction.major_premise.subject_notion.id, human.id)
        self.assertEqual(deduction.major_premise.complement_notion.id, mortal.id)
        self.assertEqual(deduction.minor_premise.subject_notion.id, socrates.id)
        self.assertEqual(deduction.minor_premise.complement_notion.id, human.id)

    def test_multiple_deductions_with_different_premises(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        man = self._create_notion("man", "man_id")
        mortal = self._create_notion("mortal", "mortal_id")

        self._create_statement(socrates, human, Relation.from_string("is"))
        self._create_statement(human, mortal, Relation.from_string("is"))
        self._create_statement(socrates, man, Relation.from_string("is"))
        self._create_statement(man, mortal, Relation.from_string("is"))

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, None, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=None,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 2)

        major_human = beliefs_by_key.get(self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=human.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        ))
        minor_human = beliefs_by_key.get(self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=human.id,
            relation=Relation.from_string("is"),
            modifier=None,
        ))
        major_man = beliefs_by_key.get(self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=man.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        ))
        minor_man = beliefs_by_key.get(self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=man.id,
            relation=Relation.from_string("is"),
            modifier=None,
        ))
        self.assertIsNotNone(major_human)
        self.assertIsNotNone(minor_human)
        self.assertIsNotNone(major_man)
        self.assertIsNotNone(minor_man)
        if major_human is None or minor_human is None or major_man is None or minor_man is None:
            self.fail("Expected major/minor premises to be available")

        pairs = {(d.major_premise, d.minor_premise) for d in deductions}
        self.assertIn((major_human, minor_human), pairs)
        self.assertIn((major_man, minor_man), pairs)

    def test_multi_step_categorical_deduction_depth(self):
        socrates = self._create_notion("socrates", "socrates_id")
        philosopher = self._create_notion("philosopher", "philosopher_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        self._create_statement(socrates, philosopher, Relation.from_string("is"))
        self._create_statement(philosopher, human, Relation.from_string("is"))
        self._create_statement(human, mortal, Relation.from_string("is"))

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, None, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=None,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=None,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertGreaterEqual(len(deductions), 1)

    def test_modifier_scopes_categorical_deductions(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        athens = Modifier(location="Athens")
        sparta = Modifier(location="Sparta")

        self._create_statement(socrates, human, Relation.from_string("is"), modifier=athens)
        self._create_statement(human, mortal, Relation.from_string("is"), modifier=athens)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=sparta)
        self._create_statement(human, mortal, Relation.from_string("is"), modifier=sparta)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, athens, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=athens,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=athens,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 1)
        modifier = deductions[0].modifier
        self.assertIsNotNone(modifier)
        if modifier is None:
            self.fail("Expected deduction modifier to be set")
        self.assertEqual(modifier.location, "Athens")

    def test_no_deduction_with_location_mismatch_when_time_matches(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        today = Time(year=2024, month=1, day=1)
        athens = Modifier(location="Athens", from_time=today)
        sparta = Modifier(location="Sparta", from_time=today)

        self._create_statement(human, mortal, Relation.from_string("is"), modifier=athens)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=sparta)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, athens, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=athens,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=athens,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNone(belief)

    def test_no_deduction_with_time_mismatch_when_location_matches(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        today = Modifier(location="Athens", from_time=Time(year=2024, month=1, day=1))
        tomorrow = Modifier(location="Athens", from_time=Time(year=2024, month=1, day=2))

        self._create_statement(human, mortal, Relation.from_string("is"), modifier=today)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=tomorrow)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, today, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=today,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=today,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNone(belief)

    def test_is_a_preserves_custom_relation(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        wisdom = self._create_notion("wisdom", "wisdom_id")

        self._create_statement(human, wisdom, Relation.from_string("has property"))
        self._create_statement(socrates, human, Relation.from_string("is"))

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, None, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=None,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=wisdom.id,
            relation=Relation.from_string("has property"),
            modifier=None,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 1)

    def test_is_a_passes_through_custom_relation(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        property_notion = self._create_notion("property", "property_id")

        self._create_statement(human, property_notion, Relation.from_string("owns"))
        self._create_statement(socrates, human, Relation.from_string("is"))

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, None, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=None,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=property_notion.id,
            relation=Relation.from_string("owns"),
            modifier=None,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 1)

    def test_no_deduction_with_mismatched_time_modifiers(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        today = Modifier(from_time=Time(year=2024, month=1, day=1))
        tomorrow = Modifier(from_time=Time(year=2024, month=1, day=2))

        self._create_statement(human, mortal, Relation.from_string("is"), modifier=today)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=tomorrow)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, today, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=today,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=today,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNone(belief)

    def test_no_deduction_with_minute_offset_modifiers(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        now = Modifier(from_time=Time(year=2024, month=1, day=1, hour=10, minute=0))
        in_two_minutes = Modifier(from_time=Time(year=2024, month=1, day=1, hour=10, minute=2))

        self._create_statement(human, mortal, Relation.from_string("is"), modifier=now)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=in_two_minutes)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, now, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=now,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=now,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNone(belief)

    def test_deduction_with_matching_time_modifiers(self):
        socrates = self._create_notion("socrates", "socrates_id")
        human = self._create_notion("human", "human_id")
        mortal = self._create_notion("mortal", "mortal_id")

        today = Modifier(from_time=Time(year=2024, month=1, day=1))

        self._create_statement(human, mortal, Relation.from_string("is"), modifier=today)
        self._create_statement(socrates, human, Relation.from_string("is"), modifier=today)

        beliefs_by_key = self.belief_service._base_beliefs_for_agent(self.agent.id, today, None)
        self.categorical_deduction_service.resolve_deductions(
            beliefs_by_key,
            modifier=today,
            max_depth=5,
        )
        belief_key = self.belief_service._belief_key_from_values(
            agent_id=self.agent.id,
            subject_id=socrates.id,
            object_id=mortal.id,
            relation=Relation.from_string("is"),
            modifier=today,
        )
        belief = beliefs_by_key.get(belief_key)
        self.assertIsNotNone(belief)
        deductions = self._deductions_from_belief(belief)
        self.assertEqual(len(deductions), 1)
        modifier = deductions[0].modifier
        self.assertIsNotNone(modifier)
        if modifier is None:
            self.fail("Expected deduction modifier to be set")
        from_time = modifier.from_time
        self.assertIsNotNone(from_time)
        if from_time is None:
            self.fail("Expected deduction from_time to be set")
        self.assertEqual(from_time.day, 1)


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