from unittest import TestCase
import unittest
import uuid
from datetime import datetime, timedelta
from analogai.foundation.modules.notion.notion import Notion
from analogai.foundation.modules.user.user import User
from analogai.foundation.modules.direct_statement.direct_statement import DirectStatement as Statement
from analogai.foundation.modules.categorical_deduction.categorical_deduction import CategoricalDeduction
from analogai.foundation.common.dtos.notion_expression import NotionExpression
from analogai.foundation.tests.functional.services.setup import ServicesTestConfig
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.attribute import Attribute

class TestBeliefServiceFunctional(TestCase):
	def setUp(self):
		self.config = ServicesTestConfig()
		self.categorical_deduction_service = self.config.categorical_deduction_service
		self.notion_service = self.config.notion_service
		self.belief_service = self.config.belief_service
		self.statement_service = self.config.statement_service
		self.config.tear_down()
		self.user = self.config.user
		self.agent = self.config.agent

	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.config.notion_repository.create(notion)
		return notion

	def _create_statement(self, subject_notion: Notion, complement_notion: Notion, stmt_id: str, probability: float = 0.8, validity: float = 0.9, user_id: str = None) -> Statement:
		if user_id is None:
			user_id = self.user.id
		return self.statement_service.create(
			user_id=user_id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			complement_notion=complement_notion,
			relation=Relation.from_string("is"),
			probability=probability,
			validity=validity,
			id=stmt_id
		)

	def test_add_statement_to_composition_new_belief(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		result = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIsNotNone(result)
		self.assertEqual(result.subject_notion.id, "human_id")
		self.assertEqual(result.complement_notion.id, "mortal_id")
		self.assertEqual(result.relation, Relation.from_string("is"))
		self.assertEqual(len(result.statements), 1)
		self.assertEqual(result.statements[0].id, "stmt_id")

	def test_add_statement_to_composition_existing_belief(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertEqual(len(belief.statements), 1, "Belief object should have one statement initially")
		self.assertEqual(belief.statements[0].id, "stmt_id", "Initial statement ID should match")
		second_user_id = str(uuid.uuid4())
		self._create_statement(subject_notion, complement_notion, "new_stmt_id", probability=0.9, validity=0.95, user_id=second_user_id)
		result = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIsNotNone(result)
		self.assertEqual(len(result.statements), 2, "Belief object should have two statements after adding")
		self.assertEqual({s.id for s in result.statements}, {"stmt_id", "new_stmt_id"}, "Statement IDs should match")

	def test_find(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		result = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIsNotNone(result)
		self.assertEqual(len(result.statements), 1)
		self.assertEqual(result.statements[0].id, "stmt_id")

	def test_add_statement_to_belief_creates_belief_with_relation(self):
		subject_notion = self._create_notion("socrates", "socrates_id")
		complement_notion = self._create_notion("iphone", "iphone_id")
		statement = self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			relation=Relation.from_string("sell"),
			complement_notion=complement_notion,
			probability=0.9,
			validity=0.95,
		)
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("sell"))
		self.assertIsNotNone(belief)
		self.assertEqual(belief.subject_notion.id, "socrates_id")
		self.assertEqual(belief.complement_notion.id, "iphone_id")
		self.assertEqual(belief.relation, Relation.from_string("sell"))
		self.assertEqual(len(belief.statements), 1)
		self.assertEqual(belief.statements[0].id, statement.id)

	def test_add_statement_with_quantity_to_belief_sets_belief_quantity(self):
		subject_notion = self._create_notion("socrates", "socrates_id")
		complement_notion = self._create_notion("iphone", "iphone_id")
		self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			relation=Relation.from_string("sell"),
			complement_notion=complement_notion,
			quantity=2.0,
			probability=0.9,
			validity=0.95,
		)
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("sell"), quantity=2.0)
		self.assertIsNotNone(belief.modifier)
		if belief.modifier is None:
			self.fail("Expected belief modifier to be set")
		self.assertEqual(belief.modifier.quantity, 2.0)
		retrieved_without_quantity = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("sell"), quantity=None)
		self.assertIsNone(retrieved_without_quantity)

	def test_find_not_found(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		result = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("causes"))
		self.assertIsNone(result)

	def test_find_by_expressions_not_found(self):
		self._create_notion("human", "human_id")
		self._create_notion("immortal", "immortal_id")
		result = self.belief_service.find_by_expressions(
			agent_id=self.agent.id,
			subject_notion_expression=NotionExpression(caption="human"),
			complement_notion_expression=NotionExpression(caption="immortal"),
			relation=Relation.from_string("is")
		)
		self.assertIsNone(result)

	def test_search_by_notions(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			complement_notion=complement_notion,
			relation=Relation.from_string("causes"),
			probability=0.8,
			validity=0.9,
			id="stmt2_id"
		)
		results = self.belief_service.search_by_notions(subject_notion, complement_notion)
		self.assertEqual(len(results), 2)
		self.assertEqual({b.relation for b in results}, {Relation.from_string("is"), Relation.from_string("causes")})

	def test_search_by_subject_notion(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		other_notion = self._create_notion("animal", "animal_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		self._create_statement(subject_notion, other_notion, "stmt2_id")
		results = self.belief_service.search_by_subject_notion(subject_notion)
		self.assertEqual(len(results), 2)
		self.assertEqual({b.complement_notion.id for b in results}, {"mortal_id", "animal_id"})

	def test_search_by_complement_notion(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		other_notion = self._create_notion("god", "god_id")
		self._create_statement(subject_notion, complement_notion, "stmt_id")
		self._create_statement(other_notion, complement_notion, "stmt2_id")
		results = self.belief_service.search_by_complement_notion(complement_notion)
		self.assertEqual(len(results), 2)
		self.assertEqual({b.subject_notion.id for b in results}, {"human_id", "god_id"})

	def test_find_preserves_notion_attributes(self):
		subject_attributes = [
			Attribute(tag="species", value="human"),
			Attribute(tag="year", value=1990),
		]
		complement_attributes = [
			Attribute(tag="category", value="mortal"),
		]
		subject_notion = Notion(
			user_id=self.user.id,
			agent_id=self.agent.id,
			caption="human",
			id="human_attr_id",
			attributes=subject_attributes,
		)
		complement_notion = Notion(
			user_id=self.user.id,
			agent_id=self.agent.id,
			caption="mortal",
			id="mortal_attr_id",
			attributes=complement_attributes,
		)
		self.config.notion_repository.create(subject_notion)
		self.config.notion_repository.create(complement_notion)
		self._create_statement(subject_notion, complement_notion, "stmt_attr_id")
		result = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIsNotNone(result)
		self.assertEqual(result.subject_notion.attributes, subject_attributes)
		self.assertEqual(result.complement_notion.attributes, complement_attributes)

	# def test_forget(self):
	# 	subject_notion = self._create_notion("human", "human_id")
	# 	complement_notion = self._create_notion("mortal", "mortal_id")
	# 	statement = self._create_statement(subject_notion, complement_notion, "stmt_id")
	# 	belief = self.belief_service.add_statement_to_composition(statement)
	# 	self.belief_service.forget(belief)
	# 	retrieved = self.belief_repository.find_by_id(belief.id)
	# 	self.assertIsNone(retrieved)

	def test_add_statement_and_categorical_deduction_to_belief(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		animal_notion = self._create_notion("animal", "animal_id")
		
		statement = self._create_statement(subject_notion, complement_notion, "stmt_id")
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		
		self.assertIsNotNone(belief, "Belief should be created")
		self.assertEqual(len(belief.statements), 1, "Belief should have one statement")
		self.assertEqual(belief.statements[0].id, "stmt_id", "Statement ID should match")
		from analogai.foundation.modules.direct_statement.direct_statement import DirectStatement
		self.assertIsInstance(belief.statements[0], DirectStatement, "First statement should be a statement")
		
		major_premise_statement = self._create_statement(animal_notion, complement_notion, "major_stmt_id", probability=1.0, validity=0.95)
		major_premise = self.belief_service.find(animal_notion, complement_notion, Relation.from_string("is"))
		
		minor_premise_statement = self._create_statement(subject_notion, animal_notion, "minor_stmt_id", probability=0.85, validity=1.0)
		minor_premise = self.belief_service.find(subject_notion, animal_notion, Relation.from_string("is"))
		
		updated_belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		
		self.assertIsNotNone(updated_belief, "Updated belief should exist")
		self.assertEqual(len(updated_belief.statements), 2, "Belief should include base statement and deduction")
		self.assertTrue(any(isinstance(stmt, DirectStatement) for stmt in updated_belief.statements))
		self.assertTrue(any(isinstance(stmt, CategoricalDeduction) for stmt in updated_belief.statements))

	def test_get_all_for_agent(self):
		subject_notion1 = self._create_notion("human", "human_id")
		complement_notion1 = self._create_notion("mortal", "mortal_id")
		self._create_statement(subject_notion1, complement_notion1, "stmt_id_1")
		
		subject_notion2 = self._create_notion("bird", "bird_id")
		complement_notion2 = self._create_notion("animal", "animal_id")
		self._create_statement(subject_notion2, complement_notion2, "stmt_id_2")
		
		results = self.belief_service.get_all_for_agent(self.agent.id)
		
		self.assertEqual(len(results), 2)
		belief_ids = {b.id for b in results}
		self.assertGreater(len(belief_ids), 0)
		
		for belief in results:
			self.assertEqual(belief.agent_id, self.agent.id)
			self.assertGreater(len(belief.statements), 0)

	def test_get_recently_updated_returns_recent_beliefs(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		
		statement = self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			complement_notion=complement_notion,
			relation=Relation.from_string("is"),
			probability=0.8,
			validity=0.9
		)
		
		one_minute_ago = datetime.now() - timedelta(minutes=1)
		recent_beliefs = self.belief_service.get_recently_updated(self.agent.id, one_minute_ago)
		self.assertGreater(len(recent_beliefs), 0, "Should find recently updated beliefs")
		belief_ids = {b.id for b in recent_beliefs}
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIn(belief.id, belief_ids, "The recently created belief should be in the results")

	def test_get_recently_updated_includes_updated_beliefs(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		
		statement1 = self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			complement_notion=complement_notion,
			relation=Relation.from_string("is"),
			probability=0.8,
			validity=0.9
		)
		
		statement2 = self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion,
			complement_notion=complement_notion,
			relation=Relation.from_string("is"),
			probability=0.9,
			validity=0.95
		)
		
		one_minute_ago = datetime.now() - timedelta(minutes=1)
		recent_beliefs = self.belief_service.get_recently_updated(self.agent.id, one_minute_ago)
		recent_belief_ids = {b.id for b in recent_beliefs}
		updated_belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		
		self.assertIn(updated_belief.id, recent_belief_ids, "Recently updated belief should be in results")

	def test_get_recently_updated_filters_by_agent_id(self):
		second_user = User(id=str(uuid.uuid4()), name="Second User", email="second@example.com")
		self.config.user_repository.create(second_user)
		second_agent = self.config.agent_service.create(
			creator=second_user,
			id=str(uuid.uuid4()),
			knowledge_base="Second KB",
			roles=[]
		)
		
		subject_notion1 = self._create_notion("human", "human_id")
		complement_notion1 = self._create_notion("mortal", "mortal_id")
		
		self.statement_service.create(
			user_id=self.user.id,
			agent_id=self.agent.id,
			subject_notion=subject_notion1,
			complement_notion=complement_notion1,
			relation=Relation.from_string("is"),
			probability=0.8,
			validity=0.9
		)
		
		notion2 = Notion(user_id=second_user.id, agent_id=second_agent.id, caption="bird", id="bird_id")
		notion3 = Notion(user_id=second_user.id, agent_id=second_agent.id, caption="animal", id="animal_id")
		self.config.notion_repository.create(notion2)
		self.config.notion_repository.create(notion3)
		
		self.statement_service.create(
			user_id=second_user.id,
			agent_id=second_agent.id,
			subject_notion=notion2,
			complement_notion=notion3,
			relation=Relation.from_string("is"),
			probability=0.8,
			validity=0.9
		)
		
		one_minute_ago = datetime.now() - timedelta(minutes=1)
		recent_beliefs = self.belief_service.get_recently_updated(self.agent.id, one_minute_ago)
		recent_belief_ids = {b.id for b in recent_beliefs}
		belief1 = self.belief_service.find(subject_notion1, complement_notion1, Relation.from_string("is"))
		belief2 = self.belief_service.find(notion2, notion3, Relation.from_string("is"))
		
		self.assertIn(belief1.id, recent_belief_ids, "First agent's belief should be in results")
		self.assertNotIn(belief2.id, recent_belief_ids, "Second agent's belief should not be in results")

	def test_delete_removes_attached_statements(self):
		subject_notion = self._create_notion("human", "human_id")
		complement_notion = self._create_notion("mortal", "mortal_id")
		statement = self._create_statement(subject_notion, complement_notion, "stmt_delete_id")
		belief = self.belief_service.find(subject_notion, complement_notion, Relation.from_string("is"))
		self.assertIsNotNone(belief)
		if belief is None:
			self.fail("Expected belief to be created")

		self.belief_service.delete(belief)

		self.assertIsNone(
			self.statement_service.find_by_id(statement.id),
			"Expected belief delete to remove attached statement",
		)

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