from unittest import TestCase
import unittest

from analogai.foundation.common.dtos.notion_expression import NotionExpression
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.effect import Effect
from analogai.foundation.core.value_objects.cause import Cause
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.common.enums.criterion import Criterion
from analogai.foundation.common.enums.certainty import Certainty
from analogai.foundation.modules.conditionals.hypothetical_statement import HypotheticalStatement
from analogai.foundation.modules.conditionals.conditional_deduction import ConditionalDeduction
from analogai.foundation.tests.functional.services.setup import ServicesTestConfig


class TestConditionalDeductionServiceFunctional(TestCase):
    def setUp(self):
        self.config = ServicesTestConfig()
        self.user = self.config.user
        self.agent = self.config.agent
        self.notion_service = self.config.notion_service
        self.statement_service = self.config.statement_service
        self.belief_service = self.config.belief_service
        self.conditional_deduction_service = self.config.conditional_deduction_service
        self.hypothetical_statement_service = self.config.hypothetical_statement_service

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

    def test_create_returns_conditional_deduction(self):
        subject = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="sky"),
        )
        obj = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="blue"),
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=subject,
            complement_notion=obj,
            relation=Relation.from_string("is"),
            probability=0.8,
            validity=0.9,
        )
        hypothetical = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "sky",
                    Criterion.ObjectCaption: "blue",
                    Criterion.Relation: Relation.from_string("is"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "sky",
                Criterion.ObjectCaption: "blue",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Certainty: Certainty.UNCERTAIN,
            }),
            validity=0.65,
        )
        modifier = Modifier(location="outdoors")

        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=modifier,
            user_id=self.user.id,
            hypothetical_statements=[hypothetical],
        )

        self.assertEqual(len(deductions), 1)
        deduction = deductions[0]

        self.assertEqual(deduction.subject_notion.id, subject.id)
        self.assertEqual(deduction.complement_notion.id, obj.id)
        self.assertEqual(deduction.relation, Relation.from_string("is"))
        self.assertEqual(deduction.probability, 0.5)
        self.assertEqual(deduction.validity, 0.65)
        self.assertIsNotNone(deduction.modifier)
        if deduction.modifier is None:
            self.fail("Expected modifier to be preserved on deduction")
        self.assertEqual(deduction.modifier.location, "outdoors")

    def test_find_deductions_by_agent_id_returns_deductions(self):
        subject = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="cloud"),
        )
        obj = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="gray"),
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=subject,
            complement_notion=obj,
            relation=Relation.from_string("is"),
            probability=0.8,
            validity=0.9,
        )

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "cloud",
                    Criterion.ObjectCaption: "gray",
                    Criterion.Relation: Relation.from_string("is"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "cloud",
                Criterion.ObjectCaption: "storm",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )

        deductions = self.conditional_deduction_service.find_deductions_by_agent_id(
            agent_id=self.agent.id,
            modifier=None,
            user_id=self.user.id,
        )

        self.assertGreaterEqual(len(deductions), 1)
        matching = [
            d for d in deductions
            if d.hypothetical_statement.id == hypothetical.id
            and d.subject_notion.caption == "cloud"
            and d.complement_notion.caption == "storm"
        ]
        self.assertEqual(len(matching), 1)

    def test_create_returns_empty_when_no_causes_match(self):
        hypothetical = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "rain",
                    Criterion.ObjectCaption: "wet",
                    Criterion.Relation: Relation.from_string("causes"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "sky",
                Criterion.ObjectCaption: "blue",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )
        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=None,
            user_id=self.user.id,
            hypothetical_statements=[hypothetical],
        )
        self.assertEqual(deductions, [])

    def test_create_returns_conditional_deduction_with_multiple_causes(self):
        sky = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="sky"),
        )
        blue = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="blue"),
        )
        grass = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="grass"),
        )
        green = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="green"),
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=sky,
            complement_notion=blue,
            relation=Relation.from_string("is"),
            probability=0.9,
            validity=0.9,
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=grass,
            complement_notion=green,
            relation=Relation.from_string("is"),
            probability=0.9,
            validity=0.9,
        )

        hypothetical = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "sky",
                    Criterion.ObjectCaption: "blue",
                    Criterion.Relation: Relation.from_string("is"),
                }),
                Cause({
                    Criterion.SubjectCaption: "grass",
                    Criterion.ObjectCaption: "green",
                    Criterion.Relation: Relation.from_string("is"),
                }),
            ],
            effect=Effect({
                Criterion.SubjectCaption: "day",
                Criterion.ObjectCaption: "sunny",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )

        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=None,
            user_id=self.user.id,
            hypothetical_statements=[hypothetical],
        )

        self.assertEqual(len(deductions), 1)
        deduction = deductions[0]
        self.assertEqual(deduction.subject_notion.caption, "day")
        self.assertEqual(deduction.complement_notion.caption, "sunny")

    def test_create_returns_causal_deduction(self):
        smoking = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="smoking"),
        )
        cancer = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="cancer"),
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=smoking,
            complement_notion=cancer,
            relation=Relation.from_string("causes"),
            probability=0.8,
            validity=0.8,
        )

        hypothetical = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "smoking",
                    Criterion.ObjectCaption: "cancer",
                    Criterion.Relation: Relation.from_string("causes"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "smoking",
                Criterion.ObjectCaption: "death",
                Criterion.Relation: Relation.from_string("causes"),
                Criterion.Certainty: Certainty.UNCERTAIN,
            }),
        )

        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=None,
            user_id=self.user.id,
            hypothetical_statements=[hypothetical],
        )

        self.assertEqual(len(deductions), 1)
        deduction = deductions[0]
        self.assertEqual(deduction.relation, Relation.from_string("causes"))
        self.assertEqual(deduction.subject_notion.caption, "smoking")
        self.assertEqual(deduction.complement_notion.caption, "death")

    def test_conditional_deduction_with_location_modifier(self):
        it_notion = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="it"),
        )
        rain_notion = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="rain"),
        )

        paris_modifier = Modifier(location="paris")
        rome_modifier = Modifier(location="rome")

        hypothetical = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "it",
                    Criterion.ObjectCaption: "rain",
                    Criterion.Relation: Relation.from_string("is"),
                    Criterion.Location: "paris",
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "it",
                Criterion.ObjectCaption: "rain",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Location: "rome",
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )

        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=it_notion,
            complement_notion=rain_notion,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
            modifier=paris_modifier,
        )

        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=None,
            user_id=self.user.id,
            hypothetical_statements=[hypothetical],
        )

        self.assertEqual(len(deductions), 1)
        deduction = deductions[0]
        self.assertEqual(deduction.subject_notion.id, it_notion.id)
        self.assertEqual(deduction.complement_notion.id, rain_notion.id)
        self.assertEqual(deduction.relation, Relation.from_string("is"))
        self.assertIsNotNone(deduction.modifier)
        if deduction.modifier is None:
            self.fail("Expected modifier to be preserved on deduction")
        self.assertEqual(deduction.modifier.location, "rome")

    def test_conditional_deduction_with_time_window_criteria(self):
        rain = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="rain"),
        )
        wet = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="wet"),
        )
        floods = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="floods"),
        )

        from_time = Time(year=2024, month=3, day=1)
        to_time = Time(year=2024, month=3, day=31)
        modifier = Modifier(from_time=from_time, to_time=to_time)
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=rain,
            complement_notion=wet,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
            modifier=modifier,
        )

        matching = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "rain",
                    Criterion.ObjectCaption: "wet",
                    Criterion.Relation: Relation.from_string("is"),
                    Criterion.FromTime: from_time,
                    Criterion.ToTime: to_time,
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "rain",
                Criterion.ObjectCaption: "floods",
                Criterion.Relation: Relation.from_string("causes"),
                Criterion.FromTime: from_time,
                Criterion.ToTime: to_time,
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )
        mismatched = HypotheticalStatement(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "rain",
                    Criterion.ObjectCaption: "wet",
                    Criterion.Relation: Relation.from_string("is"),
                    Criterion.FromTime: Time(year=2024, month=3, day=2),
                    Criterion.ToTime: to_time,
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "rain",
                Criterion.ObjectCaption: "floods",
                Criterion.Relation: Relation.from_string("causes"),
                Criterion.Certainty: Certainty.CERTAIN,
            }),
        )

        deductions = self.conditional_deduction_service.resolve_deductions(
            agent_id=self.agent.id,
            beliefs=self.belief_service.get_all_for_agent(self.agent.id),
            modifier=None,
            user_id=self.user.id,
            hypothetical_statements=[matching, mismatched],
        )

        self.assertGreaterEqual(len(deductions), 1)
        matching_deductions = [
            d for d in deductions
            if d.subject_notion.id == rain.id
            and d.complement_notion.id == floods.id
            and d.relation == Relation.from_string("causes")
        ]
        self.assertGreaterEqual(len(matching_deductions), 1)
        deduction = matching_deductions[0]
        self.assertIsNotNone(deduction.modifier)
        if deduction.modifier is None:
            self.fail("Expected modifier to be preserved on deduction")
        self.assertIsNotNone(deduction.modifier.from_time)
        self.assertIsNotNone(deduction.modifier.to_time)
        self.assertEqual(deduction.modifier.from_time.day, 1)
        self.assertEqual(deduction.modifier.to_time.day, 31)

    def test_conditional_deduction_included_in_beliefs_with_probability_from_criteria(self):
        subject = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="fire"),
        )
        obj = self.notion_service.find_or_create(
            self.user.id,
            self.agent.id,
            NotionExpression(caption="hot"),
        )
        self.statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            subject_notion=subject,
            complement_notion=obj,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
        )

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user.id,
            agent_id=self.agent.id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "fire",
                    Criterion.ObjectCaption: "hot",
                    Criterion.Relation: Relation.from_string("is"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "fire",
                Criterion.ObjectCaption: "hot",
                Criterion.Relation: Relation.from_string("is"),
                Criterion.Certainty: Certainty.NEGATED,
            }),
        )

        beliefs = self.belief_service.get_all_for_agent(self.agent.id)
        matching = [
            belief for belief in beliefs
            if belief.subject_notion.id == subject.id
            and belief.complement_notion.id == obj.id
            and belief.relation == Relation.from_string("is")
        ]
        self.assertTrue(matching, "Expected belief for fire is hot")

        belief = matching[0]
        deductions = [
            stmt for stmt in belief.statements
            if isinstance(stmt, ConditionalDeduction)
            and stmt.hypothetical_statement.id == hypothetical.id
        ]
        self.assertEqual(len(deductions), 1)
        self.assertEqual(deductions[0].probability, 0.0)


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