import unittest

import analogai.foundation.extensions.belief_query.tests.env_setup
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.dtos.notion_expression import NotionExpression
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.attribute import Attribute
from analogai.foundation.common.enums.criterion import Criterion
from analogai.foundation.common.enums.certainty import Certainty
from analogai.foundation.setup.factories import BeliefQueryServiceFactory
from analogai.foundation.tests.functional.services.setup import ServicesTestConfig


def create_learning(
    learnings_service,
    user_id,
    agent_id,
    subject_notion,
    result_criteria,
    modifier=None,
    probability=None,
    validity=None,
    statement_service=None,
    notion_service=None,
    belief_service=None,
):
    relation = result_criteria.get(Criterion.Relation)
    object_name = result_criteria.get(Criterion.ObjectCaption)
    if not relation or not object_name:
        raise ValueError("Belief result requires Relation and ObjectCaption")
    if not isinstance(relation, Relation):
        raise TypeError("Criterion.Relation must be a Relation instance")
    if statement_service is None or notion_service is None:
        raise ValueError("statement_service and notion_service are required to create learning statements")
    if belief_service is None:
        belief_service = learnings_service._belief_service
    attributes = None
    attr_values = result_criteria.get(Criterion.AttributeValue)
    if attr_values:
        attributes = []
        for item in attr_values:
            if isinstance(item, Attribute):
                attributes.append(item)
                continue
            if isinstance(item, dict):
                tag = item.get("tag") or item.get("kind")
                value = item.get("value") or item.get("min_value") or item.get("max_value")
                unit = item.get("unit")
                if tag is not None and value is not None:
                    attributes.append(
                        Attribute(tag=str(tag), value=value, unit=str(unit) if unit else None)
                    )
    complement_notion = notion_service.find_or_create(
        user_id,
        agent_id,
        NotionExpression(caption=str(object_name), attributes=attributes),
    )
    relation_obj = relation
    prob = probability if probability is not None else result_criteria.get(Criterion.RelationProbability, 1.0)
    valid = validity if validity is not None else 1.0
    final_modifier = modifier
    statement_service.create(
        user_id=user_id,
        agent_id=agent_id,
        subject_notion=subject_notion,
        complement_notion=complement_notion,
        relation=relation_obj,
        probability=prob,
        validity=valid,
        modifier=final_modifier,
    )
    return belief_service.find(
        subject_notion=subject_notion,
        complement_notion=complement_notion,
        relation=relation_obj,
        modifier=final_modifier,
    )


class TestBeliefQueryService(unittest.TestCase):
    def setUp(self):
        self.config = ServicesTestConfig()
        self.notion_service = self.config.notion_service
        self.statement_service = self.config.statement_service
        self.belief_service = self.config.belief_service
        self.hypothetical_statement_service = self.config.hypothetical_statement_service
        self.service = BeliefQueryServiceFactory(
            belief_service=self.belief_service,
        ).get_service()
        self.user_id = self.config.user.id
        self.agent_id = self.config.agent.id
        self.subject_notion = self.notion_service.find_or_create(
            self.user_id, self.agent_id, NotionExpression(caption="socrates")
        )

    def _search(self):
        return self.service.search(self.user_id, self.agent_id).exactModifierMatch()

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

    def test_create_belief_returns_learning_with_dynamic_resolution(self):
        result_criteria = {
            Criterion.Relation: Relation.from_string("is"),
            Criterion.ObjectCaption: "human",
        }
        belief = create_learning(
            self.service,
            self.user_id,
            self.agent_id,
            self.subject_notion,
            result_criteria,
            statement_service=self.statement_service,
            notion_service=self.notion_service,
        )
        self.assertEqual(belief.subject_notion.caption, "socrates")
        self.assertEqual(belief.complement_notion.caption, "human")
        self.assertEqual(belief.relation, Relation.from_string("is"))

    def test_create_statement_returns_learning_with_dynamic_resolution(self):
        result_criteria = {
            Criterion.Relation: Relation.from_string("sells"),
            Criterion.ObjectCaption: "iphone",
        }
        belief = create_learning(
            self.service,
            self.user_id,
            self.agent_id,
            self.subject_notion,
            result_criteria,
            statement_service=self.statement_service,
            notion_service=self.notion_service,
        )
        self.assertEqual(belief.subject_notion.caption, "socrates")
        self.assertEqual(belief.relation, Relation.from_string("sells"))
        self.assertEqual(belief.complement_notion.caption, "iphone")

    def test_search_includes_categorical_deductions(self):
        human = self.notion_service.find_or_create(
            self.user_id, self.agent_id, NotionExpression(caption="human")
        )
        mortal = self.notion_service.find_or_create(
            self.user_id, self.agent_id, NotionExpression(caption="mortal")
        )
        self.statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            subject_notion=self.subject_notion,
            complement_notion=human,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
        )
        self.statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            subject_notion=human,
            complement_notion=mortal,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
        )

        results = self._search().where(
            Criterion.SubjectCaption, "socrates"
        ).where(
            Criterion.Relation, Relation.from_string("is")
        ).where(
            Criterion.ObjectCaption, "mortal"
        ).list()

        self.assertEqual(len(results), 1)

    def test_search_includes_categorical_and_conditional_deductions(self):
        human = self.notion_service.find_or_create(
            self.user_id, self.agent_id, NotionExpression(caption="human")
        )
        mortal = self.notion_service.find_or_create(
            self.user_id, self.agent_id, NotionExpression(caption="mortal")
        )
        self.statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            subject_notion=self.subject_notion,
            complement_notion=human,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
        )
        self.statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            subject_notion=human,
            complement_notion=mortal,
            relation=Relation.from_string("is"),
            probability=1.0,
            validity=1.0,
        )
        self.hypothetical_statement_service.create(
            user_id=self.user_id,
            agent_id=self.agent_id,
            causes=[
                Cause({
                    Criterion.SubjectCaption: "socrates",
                    Criterion.ObjectCaption: "human",
                    Criterion.Relation: Relation.from_string("is"),
                })
            ],
            effect=Effect({
                Criterion.SubjectCaption: "socrates",
                Criterion.ObjectCaption: "wisdom",
                Criterion.Relation: Relation.from_string("causes"),
                Criterion.Certainty: Certainty.UNCERTAIN,
            }),
        )

        categorical_results = self._search().where(
            Criterion.SubjectCaption, "socrates"
        ).where(
            Criterion.Relation, Relation.from_string("is")
        ).where(
            Criterion.ObjectCaption, "mortal"
        ).list()

        conditional_results = self._search().where(
            Criterion.SubjectCaption, "socrates"
        ).where(
            Criterion.Relation, Relation.from_string("causes")
        ).where(
            Criterion.ObjectCaption, "wisdom"
        ).list()

        self.assertEqual(len(categorical_results), 1)
        self.assertEqual(len(conditional_results), 1)


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