#!/usr/bin/env python3
"""Unit tests for MyCalculation implementation."""

import unittest
from unittest.mock import Mock, patch, MagicMock
import logging

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))

from calculation.impl.my_calculation import MyCalculation
from calculation.base import CalculationBase


class TestMyCalculation(unittest.TestCase):
    """Test cases for MyCalculation class."""

    def setUp(self):
        """Set up test fixtures."""
        # Create mock logger
        self.mock_logger = Mock(spec=logging.Logger)

        # Sample test data
        self.test_data = {
            'numbers': [1, 2, 3, 4, 5]
        }

    def test_initialization(self):
        """Test MyCalculation initialization with logger and kwargs."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Verify inheritance
        self.assertIsInstance(calc, CalculationBase)

        # Verify logger is set
        self.assertEqual(calc._logger, self.mock_logger)

        # Verify kwargs are stored
        self.assertEqual(calc._kwargs, {'data': self.test_data})

        # Verify initial state
        self.assertIsNone(calc._result)
        self.assertFalse(calc._is_running)

        # Verify initialization logging
        self.mock_logger.info.assert_called_with("MyCalculation instance created")

    def test_initialization_without_data_key(self):
        """Test MyCalculation initialization with kwargs as data."""
        calc = MyCalculation(self.mock_logger, numbers=[10, 20, 30])

        # Verify kwargs are stored
        self.assertEqual(calc._kwargs, {'numbers': [10, 20, 30]})

    def test_set_up_valid_data(self):
        """Test set_up with valid data structure."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Call set_up
        calc.set_up()

        # Verify logging
        self.mock_logger.info.assert_any_call("MyCalculation setup started")
        self.mock_logger.info.assert_any_call("MyCalculation setup completed successfully")

    def test_set_up_invalid_data_warning(self):
        """Test set_up with non-dict data logs warning."""
        calc = MyCalculation(self.mock_logger, data="invalid_string")

        # Call set_up
        calc.set_up()

        # Verify warning was logged
        self.mock_logger.warning.assert_called()
        warning_call = self.mock_logger.warning.call_args[0][0]
        self.assertIn("Expected dict data", warning_call)

    @patch('calculation.impl.my_calculation.calculate')
    def test_run_successful_calculation(self, mock_calculate):
        """Test run() executes calculation successfully."""
        # Mock calculate function return value
        expected_result = {
            'result': 15,
            'count': 5,
            'average': 3.0,
            'timestamp': '2025-10-12T12:00:00',
            'service_name': 'test-service'
        }
        mock_calculate.return_value = expected_result

        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Call run
        calc.run()

        # Verify calculate was called with correct data
        mock_calculate.assert_called_once_with(self.test_data)

        # Verify result is stored
        self.assertEqual(calc._result, expected_result)

        # Verify running flag
        self.assertFalse(calc._is_running)  # Should be False after completion

        # Verify logging
        self.mock_logger.info.assert_any_call("MyCalculation execution started")
        self.mock_logger.info.assert_any_call(f"Calculation completed successfully: {expected_result}")

    @patch('calculation.impl.my_calculation.calculate')
    def test_run_with_data_in_kwargs_root(self, mock_calculate):
        """Test run() when data is at kwargs root level (not in 'data' key)."""
        expected_result = {'result': 60}
        mock_calculate.return_value = expected_result

        # Pass data at root level
        calc = MyCalculation(self.mock_logger, numbers=[10, 20, 30])

        # Call run
        calc.run()

        # Verify calculate was called with root-level kwargs
        mock_calculate.assert_called_once_with({'numbers': [10, 20, 30]})

        # Verify result
        self.assertEqual(calc._result, expected_result)

    @patch('calculation.impl.my_calculation.calculate')
    def test_run_calculation_exception(self, mock_calculate):
        """Test run() handles calculation exceptions."""
        # Mock calculate to raise exception
        mock_calculate.side_effect = ValueError("Invalid input data")

        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Call run and expect exception
        with self.assertRaises(ValueError):
            calc.run()

        # Verify error result is stored
        self.assertIsNotNone(calc._result)
        self.assertEqual(calc._result['error'], "Invalid input data")
        self.assertEqual(calc._result['status'], 'failed')

        # Verify running flag is cleared
        self.assertFalse(calc._is_running)

        # Verify error logging
        self.mock_logger.error.assert_called()
        error_call = self.mock_logger.error.call_args[0][0]
        self.assertIn("Calculation failed", error_call)

    def test_tear_down(self):
        """Test tear_down cleans up resources."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)
        calc._is_running = True  # Simulate running state

        # Call tear_down
        calc.tear_down()

        # Verify running flag is cleared
        self.assertFalse(calc._is_running)

        # Verify logging
        self.mock_logger.info.assert_any_call("MyCalculation teardown started")
        self.mock_logger.info.assert_any_call("MyCalculation teardown completed")

    def test_stop_during_execution(self):
        """Test stop() interrupts calculation."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)
        calc._is_running = True  # Simulate running state
        calc._result = None

        # Call stop
        calc.stop()

        # Verify running flag is cleared
        self.assertFalse(calc._is_running)

        # Verify stopped result is set
        self.assertIsNotNone(calc._result)
        self.assertEqual(calc._result['status'], 'stopped')
        self.assertIn('message', calc._result)

        # Verify logging
        self.mock_logger.warning.assert_called_with("MyCalculation stop requested")
        self.mock_logger.info.assert_called_with("MyCalculation stopped")

    def test_stop_after_completion(self):
        """Test stop() when result already exists."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)
        calc._result = {'result': 15}  # Simulate completed calculation

        # Call stop
        calc.stop()

        # Verify original result is preserved
        self.assertEqual(calc._result, {'result': 15})
        self.assertFalse(calc._is_running)

    def test_result_property_before_execution(self):
        """Test result property returns None before execution."""
        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Get result
        result = calc.result

        # Verify None is returned
        self.assertIsNone(result)

    @patch('calculation.impl.my_calculation.calculate')
    def test_result_property_after_execution(self, mock_calculate):
        """Test result property returns calculation result after execution."""
        expected_result = {'result': 15, 'count': 5}
        mock_calculate.return_value = expected_result

        calc = MyCalculation(self.mock_logger, data=self.test_data)
        calc.run()

        # Get result
        result = calc.result

        # Verify correct result is returned
        self.assertEqual(result, expected_result)

    @patch('calculation.impl.my_calculation.calculate')
    def test_full_lifecycle(self, mock_calculate):
        """Test complete calculation lifecycle: set_up → run → tear_down."""
        mock_calculate.return_value = {'result': 42}

        calc = MyCalculation(self.mock_logger, data=self.test_data)

        # Execute full lifecycle
        calc.set_up()
        calc.run()
        calc.tear_down()

        # Verify result is available
        self.assertEqual(calc.result, {'result': 42})

        # Verify all lifecycle methods logged
        info_calls = [call[0][0] for call in self.mock_logger.info.call_args_list]
        self.assertIn("MyCalculation setup started", info_calls)
        self.assertIn("MyCalculation execution started", info_calls)
        self.assertIn("MyCalculation teardown started", info_calls)

    def test_multiple_kwargs_passed_through(self):
        """Test that arbitrary kwargs are stored and used."""
        calc = MyCalculation(
            self.mock_logger,
            numbers=[1, 2, 3],
            config={'setting': 'value'},
            metadata={'user': 'test'}
        )

        # Verify all kwargs are stored
        self.assertIn('numbers', calc._kwargs)
        self.assertIn('config', calc._kwargs)
        self.assertIn('metadata', calc._kwargs)


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