"""
@file calc_tests.py
@brief Unit tests for the Calculator model logic.
@details Validates the input accumulator, arithmetic operations, error handling
for edge cases, and scientific notation.
@author Daniel Prsek
@date 2026-04-19
"""

import sys
import os
import unittest
import math

root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if root_path not in sys.path:
    sys.path.insert(0, root_path)


from calculator import Calculator, Operation, Sign


class TestCalculator(unittest.TestCase):
    """@brief Comprehensive test suite for Calculator class logic."""

    def setUp(self):
        """@brief Initialize a fresh calculator instance before each test.
        @details Sets max_digits to 10 for easier formatting testing.
        """
        self.calc = Calculator(max_digits=10)

    def test_initial_state(self):
        """@brief Verify the calculator starts in a clean state."""
        self.assertEqual(self.calc.result, 0.0)
        self.assertEqual(self.calc.accumulator, "0")
        self.assertEqual(self.calc.current_operation, Operation.EQUAL)

    def test_digit_accumulation(self):
        """@brief Test entering numbers and decimal points.
        @return Updated accumulator string check.
        """
        self.calc.add_digit(1)
        self.calc.add_digit(2)
        self.calc.add_decimal_point()
        self.calc.add_digit(3)
        self.assertEqual(self.calc.accumulator, "12.3")

    def test_add(self):
        """@brief Test basic addition operation logic."""
        self.calc.add_digit(1)
        self.calc.add_digit(0)
        self.calc.perform_operation(Operation.ADD)
        self.calc.add_digit(5)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertEqual(self.calc.result, 15.0)

    def test_subtraction(self):
        """@brief Test subtraction: 10 - 4 = 6."""
        self.calc.add_digit(1)
        self.calc.add_digit(0)
        self.calc.perform_operation(Operation.SUBTRACT)
        self.calc.add_digit(4)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertEqual(self.calc.result, 6.0)   

    def test_multiplication(self):
        """@brief Test multiplication: 3 * 7 = 21."""
        self.calc.add_digit(3)
        self.calc.perform_operation(Operation.MULTIPLY)
        self.calc.add_digit(7)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertEqual(self.calc.result, 21.0)

    def test_power(self):
        """@brief Test power operation: 2 ^ 3 = 8."""
        self.calc.add_digit(2)
        self.calc.perform_operation(Operation.POWER)
        self.calc.add_digit(3)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertEqual(self.calc.result, 8.0)

    def test_delete_digit(self):
        """@brief Test backspace functionality by removing last digit."""
        self.calc.add_digit(1)
        self.calc.add_digit(2)
        self.calc.add_digit(5)
        self.calc.delete_digit() 
        self.assertEqual(self.calc.accumulator, "12")
        self.calc.delete_digit() 
        self.assertEqual(self.calc.accumulator, "1")
        self.calc.delete_digit() 
        self.assertEqual(self.calc.accumulator, "0")

    def test_division_by_zero(self):
        """@brief Test division by zero returns NaN.
        @details Checks if result is float('nan') when dividing by zero.
        """
        self.calc.add_digit(8)
        self.calc.perform_operation(Operation.DIVIDE)
        self.calc.add_digit(0)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertTrue(math.isnan(self.calc.result))

    def test_root_negative(self):
        """@brief Test behavior when rooting negative numbers.
        @details Validates if result is either NaN or a complex number.
        """
        self.calc.result = -16.0
        self.calc.current_operation = Operation.ROOT
        self.calc.add_digit(2)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertTrue(math.isnan(self.calc.result) or isinstance(self.calc.result, complex))

    def test_sign_toggle(self):
        """@brief Test toggling between positive and negative signs."""
        self.calc.add_digit(9)
        self.calc.change_sign()
        self.assertEqual(self.calc.sign, Sign.Negative)
        self.calc.change_sign()
        self.assertEqual(self.calc.sign, Sign.Positive)

    def test_scientific_notation_formatting(self):
        """@brief Test if results are formatted to fit within MAX_DIGITS limit."""
        long_val = 1234567890123.0 
        formatted = self.calc._format_value(long_val)
        self.assertLessEqual(len(formatted), self.calc.MAX_CHARACTERS)
        self.assertIn('e', formatted)

    def test_modulo(self):
        """@brief Test the modulo operation remainder logic."""
        self.calc.add_digit(1)
        self.calc.add_digit(0)
        self.calc.perform_operation(Operation.MODULO)
        self.calc.add_digit(3)
        self.calc.perform_operation(Operation.EQUAL)
        self.assertEqual(self.calc.result, 1.0)

    def test_reset(self):
        """@brief Test full reset of the calculator state and result."""
        self.calc.add_digit(5)
        self.calc.perform_operation(Operation.ADD)
        self.calc.reset()
        self.assertEqual(self.calc.result, 0.0)
        self.assertEqual(self.calc.accumulator, "0")

    def test_result_string_format(self):
        """@brief Test that integer result 5.0 is returned as string '5'."""
        self.calc.result = 5.0
        self.assertEqual(self.calc.get_result_string(), "5")


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