import unittest
from datetime import datetime, timedelta
from unittest.mock import Mock

from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.core.entities.statement import statement
from analogai.foundation.common.design_patterns.state_machine import State
from analogai.deepthink.application.usecases.learning.make_direct_statement.states.update_statement_state import UpdateStatementState
from analogai.deepthink.application.usecases.learning.make_direct_statement.usecase import MakeDirectStatementUseCase
from analogai.deepthink.application.usecases.learning.make_direct_statement.ports import MakeStatementOutputPort
from analogai.deepthink.application.usecases.learning.make_direct_statement.states.common.response_builder import LearnerResponseBuilder

class TestUpdateStatementState(unittest.TestCase):
    def setUp(self):
        self.state_machine = Mock(spec=MakeDirectStatementUseCase)
        self.statement_service = Mock()
        self.output_port = Mock(spec=MakeStatementOutputPort)
        self.response_builder = Mock(spec=LearnerResponseBuilder)
        self.context = Mock(spec=['statement', 'proposed_probability', 'proposed_validity', 'modifier', 'user_agent'])
        
                                 
        self.statement = Mock(spec=statement)
        self.statement.id = "test-statement-id"
        self.statement.probability = 0.7
        self.statement.validity = 0.8
        self.statement.modifier = None
        
        self.context.statement = self.statement
        self.context.proposed_probability = 0.9
        self.context.proposed_validity = 0.95
        self.context.modifier = None
        self.state_machine.context = self.context
        
                                 
        self.updated_statement_after_prob = Mock(spec=statement)
        self.updated_statement_after_prob.id = "test-statement-id"
        self.updated_statement_after_prob.probability = 0.9
        self.updated_statement_after_prob.validity = 0.8
        
        self.updated_statement_final = Mock(spec=statement)
        self.updated_statement_final.id = "test-statement-id"
        self.updated_statement_final.probability = 0.9
        self.updated_statement_final.validity = 0.95
        
        self.statement_service.update_probability.return_value = self.updated_statement_after_prob
        self.statement_service.update_validity.return_value = self.updated_statement_final
        
        self.state = UpdateStatementState(
            self.state_machine,
            statement_service=self.statement_service,
            output_port=self.output_port,
            response_builder=self.response_builder
        )

    def test_init(self):
        self.assertEqual(self.state.state_machine, self.state_machine)
        self.assertEqual(self.state._statement_service, self.statement_service)
        self.assertEqual(self.state._output_port, self.output_port)
        self.assertEqual(self.state._response_builder, self.response_builder)
        self.assertIsInstance(self.state, State)

    def test_execute_should_update(self):
        self.state._validate_modifier_matches = Mock(return_value=True)
        self.state._check_should_update_statement = Mock(return_value=True)
        self.state._apply_update_and_output_success = Mock(return_value="update_success_response")
        
        result = self.state.execute()
        
        self.state._validate_modifier_matches.assert_called_once()
        self.state._check_should_update_statement.assert_called_once()
        self.state._apply_update_and_output_success.assert_called_once()
        self.assertEqual(result, "update_success_response")

    def test_execute_statement_already_known(self):
        self.state._check_is_statement_already_known = Mock(return_value=True)
        self.state._build_and_output_statement_already_known = Mock(return_value="already_known_response")
        
        result = self.state.execute()
        
        self.state._check_is_statement_already_known.assert_called_once()
        self.state._build_and_output_statement_already_known.assert_called_once()
        self.assertEqual(result, "already_known_response")

    def test_execute_should_not_update(self):
        self.state._validate_modifier_matches = Mock(return_value=True)
        self.state._check_is_statement_already_known = Mock(return_value=False)
        self.state._check_should_update_statement = Mock(return_value=False)
        self.state._build_and_output_not_applicable_response = Mock(return_value="not_applicable_response")
        
        result = self.state.execute()
        
        self.state._validate_modifier_matches.assert_called_once()
        self.state._check_should_update_statement.assert_called_once()
        self.state._build_and_output_not_applicable_response.assert_called_once()
        self.assertEqual(result, "not_applicable_response")

    def test_execute_with_mismatched_modifier_returns_not_applicable(self):
        today = datetime.now()
        tomorrow = today + timedelta(days=1)
        
        self.statement.modifier = Modifier(from_time=Time(year=today.year, month=today.month, day=today.day))
        self.context.modifier = Modifier(from_time=Time(year=tomorrow.year, month=tomorrow.month, day=tomorrow.day))
        self.state._validate_modifier_matches = Mock(return_value=False)
        self.state._build_and_output_not_applicable_response = Mock(return_value="not_applicable_response")
        
        result = self.state.execute()
        
        self.state._validate_modifier_matches.assert_called_once()
        self.state._build_and_output_not_applicable_response.assert_called_once()
        self.assertEqual(result, "not_applicable_response")

    def test_validate_modifier_matches_with_matching_contexts(self):
        today = datetime.now()
        modifier = Modifier(from_time=Time(year=today.year, month=today.month, day=today.day))
        
        self.statement.modifier = modifier
        self.context.modifier = modifier
        
        result = self.state._validate_modifier_matches()
        
        self.assertTrue(result)

    def test_validate_modifier_matches_with_mismatched_contexts(self):
        today = datetime.now()
        tomorrow = today + timedelta(days=1)
        
        self.statement.modifier = Modifier(from_time=Time(year=today.year, month=today.month, day=today.day))
        self.context.modifier = Modifier(from_time=Time(year=tomorrow.year, month=tomorrow.month, day=tomorrow.day))
        
        result = self.state._validate_modifier_matches()
        
        self.assertFalse(result)

    def test_apply_update_and_output_success_calls_service_methods(self):
        response = Mock()
        self.response_builder.build_response.return_value = response
        self.output_port.output_statement_update_success.return_value = "update_success_response"

        result = self.state._apply_update_and_output_success()

        self.statement_service.update_probability.assert_called_once_with(
            self.statement,
            0.9
        )

        self.statement_service.update_validity.assert_called_once_with(
            self.updated_statement_after_prob,
            0.95
        )

        self.assertEqual(self.context.statement, self.updated_statement_final)

        self.response_builder.build_response.assert_called_once()
        self.output_port.output_statement_update_success.assert_called_once_with(response)
        self.assertEqual(result, "update_success_response")

    def test_apply_update_and_output_success_with_no_statement(self):
        self.context.statement = None
        response = Mock()
        self.response_builder.build_response.return_value = response
        self.output_port.output_statement_update_success.return_value = "update_success_response"

        result = self.state._apply_update_and_output_success()

        self.statement_service.update_probability.assert_not_called()
        self.statement_service.update_validity.assert_not_called()

        self.response_builder.build_response.assert_called_once()
        self.output_port.output_statement_update_success.assert_called_once_with(response)
        self.assertEqual(result, "update_success_response")

    def test_check_should_update_statement_true(self):
        check_update_statement_applicable = Mock(return_value=True)
        with unittest.mock.patch('application.usecases.learning.make_direct_statement.states.update_statement_state.check_update_statement_applicable', check_update_statement_applicable):
            
            result = self.state._check_should_update_statement()
            
            check_update_statement_applicable.assert_called_once_with(0.7, 0.9, 0.8, 0.95)
            self.assertTrue(result)

    def test_check_should_update_statement_false(self):
        check_update_statement_applicable = Mock(return_value=False)
        with unittest.mock.patch('application.usecases.learning.make_direct_statement.states.update_statement_state.check_update_statement_applicable', check_update_statement_applicable):
            
            result = self.state._check_should_update_statement()
            
            check_update_statement_applicable.assert_called_once_with(0.7, 0.9, 0.8, 0.95)
            self.assertFalse(result)

    def test_check_is_statement_already_known_true(self):
        self.context.statement.probability = 0.9
        self.context.proposed_probability = 0.9
        
        result = self.state._check_is_statement_already_known()
        
        self.assertTrue(result)

    def test_check_is_statement_already_known_false(self):
        self.context.statement.probability = 0.7
        self.context.proposed_probability = 0.9
        
        result = self.state._check_is_statement_already_known()
        
        self.assertFalse(result)

    def test_build_and_output_statement_already_known(self):
        response = Mock()
        self.response_builder.build_response.return_value = response
        self.output_port.output_statement_already_known.return_value = "already_known_response"
        
        result = self.state._build_and_output_statement_already_known()
        
        self.response_builder.build_response.assert_called_once()
        self.output_port.output_statement_already_known.assert_called_once_with(response)
        self.assertEqual(result, "already_known_response")

    def test_build_and_output_not_applicable_response(self):
        response = Mock()
        self.response_builder.build_response.return_value = response
        self.output_port.output_update_not_applicable.return_value = "not_applicable_response"
        
        result = self.state._build_and_output_not_applicable_response()
        
        self.response_builder.build_response.assert_called_once()
        self.output_port.output_update_not_applicable.assert_called_once_with(response)
        self.assertEqual(result, "not_applicable_response")

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


