from unittest import TestCase
import unittest

from analogai.foundation.common.dtos.notion_expression import NotionExpression
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.cause import Cause
from analogai.foundation.core.value_objects.effect import Effect
from analogai.foundation.common.enums.criterion import Criterion
from analogai.foundation.common.enums.certainty import Certainty, certainty_from_value
from analogai.foundation.tests.functional.services.setup import ServicesTestConfig


CRITERION_SUBJECT = Criterion.SubjectCaption
CRITERION_OBJECT = Criterion.ObjectCaption
CRITERION_RELATION = Criterion.Relation
CRITERION_LOCATION = Criterion.Location
CRITERION_DEONTIC = Criterion.DeonticModality
CRITERION_FROM_TIME = Criterion.FromTime


class TestHypotheticalStatementServiceFunctional(TestCase):
    def setUp(self):
        self.config = ServicesTestConfig()
        self.user = self.config.user
        self.agent = self.config.agent
        self.user_id = self.user.id
        self.agent_id = self.agent.id

        self.notion_service = self.config.notion_service
        self.belief_service = self.config.belief_service
        self.hypothetical_statement_service = self.config.hypothetical_statement_service

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

    def test_create_does_not_create_effect_belief(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "rain",
                CRITERION_OBJECT: "today",
                CRITERION_RELATION: Relation.from_string("is"),
            })
        ]
        effect = Effect({
            CRITERION_SUBJECT: "ground",
            CRITERION_OBJECT: "wet",
            CRITERION_RELATION: Relation.from_string("is"),
            Criterion.Certainty: Certainty.UNCERTAIN,
        })

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=causes,
            effect=effect,
        )

        ground = self.notion_service.find_or_create(
            self.user_id,
            self.agent_id,
            NotionExpression(caption="ground"),
        )
        wet = self.notion_service.find_or_create(
            self.user_id,
            self.agent_id,
            NotionExpression(caption="wet"),
        )
        effect_belief = self.belief_service.find(
            ground,
            wet,
            Relation.from_string("is"),
        )

        self.assertIsNotNone(hypothetical)
        self.assertTrue(hypothetical.effect)
        self.assertIsNone(effect_belief)
        self.assertEqual(len(self.hypothetical_statement_service.find(self.agent_id)), 1)

    def test_create_stores_hypothetical_statement(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "rain",
                CRITERION_OBJECT: "today",
                CRITERION_RELATION: Relation.from_string("is"),
            })
        ]
        effect = Effect({
            CRITERION_SUBJECT: "ground",
            CRITERION_OBJECT: "wet",
            CRITERION_RELATION: Relation.from_string("is"),
            Criterion.Certainty: Certainty.UNCERTAIN,
        })

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=causes,
            effect=effect,
        )

        stored = self.hypothetical_statement_service.find(self.agent_id)

        self.assertEqual(len(stored), 1)
        self.assertEqual(stored[0].id, hypothetical.id)
        self.assertEqual(stored[0].probability, 0.5)
        stored_certainty = certainty_from_value(
            stored[0].effect.criteria.get(Criterion.Certainty)
        )
        self.assertEqual(stored_certainty, Certainty.UNCERTAIN)

    def test_create_stores_hypothetical_statement_validity(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "rain",
                CRITERION_OBJECT: "today",
                CRITERION_RELATION: Relation.from_string("is"),
            })
        ]
        effect = Effect({
            CRITERION_SUBJECT: "ground",
            CRITERION_OBJECT: "wet",
            CRITERION_RELATION: Relation.from_string("is"),
            Criterion.Certainty: Certainty.CERTAIN,
        })

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=causes,
            effect=effect,
            validity=0.55,
        )

        stored = self.hypothetical_statement_service.find(self.agent_id)

        self.assertEqual(len(stored), 1)
        self.assertEqual(stored[0].id, hypothetical.id)
        self.assertEqual(stored[0].validity, 0.55)

    def test_create_requires_effect_criteria(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "rain",
                CRITERION_OBJECT: "today",
                CRITERION_RELATION: Relation.from_string("is"),
            })
        ]
        effect = Effect({
            CRITERION_SUBJECT: "ground",
        })

        with self.assertRaises(ValueError):
            self.hypothetical_statement_service.create(
                user_id=self.user_id,
                agent_id=self.agent_id,
                causes=causes,
                effect=effect,
            )

    def test_create_preserves_effect_modifiers(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "winter",
                CRITERION_OBJECT: "paris",
                CRITERION_RELATION: Relation.from_string("in"),
            })
        ]

        effect = Effect({
            CRITERION_SUBJECT: "winter",
            CRITERION_OBJECT: "rome",
            CRITERION_RELATION: Relation.from_string("in"),
            CRITERION_LOCATION: "paris",
        })

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=causes,
            effect=effect,
        )

        self.assertEqual(hypothetical.effect.criteria[CRITERION_LOCATION], "paris")

    def test_create_preserves_multiple_effect_attributes(self):
        causes = [
            Cause({
                CRITERION_SUBJECT: "exam",
                CRITERION_OBJECT: "tomorrow",
                CRITERION_RELATION: Relation.from_string("is"),
            })
        ]

        effect = Effect({
            CRITERION_SUBJECT: "student",
            CRITERION_OBJECT: "study",
            CRITERION_RELATION: Relation.from_string("must"),
            CRITERION_LOCATION: "library",
            CRITERION_DEONTIC: "obligatory",
            CRITERION_FROM_TIME: {"year": 2026, "month": 2},
        })

        hypothetical = self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=causes,
            effect=effect,
        )

        self.assertEqual(hypothetical.effect.criteria[CRITERION_LOCATION], "library")
        self.assertEqual(hypothetical.effect.criteria[CRITERION_DEONTIC], "obligatory")
        self.assertEqual(
            hypothetical.effect.criteria[CRITERION_FROM_TIME],
            {"year": 2026, "month": 2},
        )


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