"""
@file mathlib_tests.py
@brief Systematic unit tests for each mathlib function.
@author Daniel Prsek
@date 2026-04-28
"""

import sys
import os
import unittest
import math

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

from src.mathlib import add, subtract, multiply, divide, power, root, sqrt, modulo, pi, factorial

class TestMathLibSystematic(unittest.TestCase):
    """
    @brief Suite of unit tests for the mathlib library.
    @details Each test case covers standard values, precision, 
    and boundary conditions.
    """

    def test_add(self):
        """
        @brief Test the addition function.
        @details Verifies standard sums, precision with tiny numbers, and overflow to infinity.
        """
        self.assertEqual(add(10, 5), 15.0)
        self.assertAlmostEqual(add(1e-10, 2e-10), 3e-10)
        self.assertEqual(add(1e308, 1e308), float('inf'))
        self.assertEqual(add(-1e308, -1e308), float('-inf'))

    def test_subtract(self):
        """
        @brief Test the subtraction function.
        @details Verifies difference logic, small value handling, and extreme positive/negative results.
        """
        self.assertEqual(subtract(10, 5), 5.0)
        self.assertAlmostEqual(subtract(2e-10, 1e-10), 1e-10)
        self.assertEqual(subtract(-1e308, 1e308), float('-inf'))
        self.assertEqual(subtract(1e308, -1e308), float('inf'))

    def test_multiply(self):
        """
        @brief Test the multiplication function.
        @details Verifies product calculation, underflow to zero, and overflow to infinity.
        """
        self.assertEqual(multiply(10, 5), 50.0)
        self.assertEqual(multiply(1e-200, 1e-200), 0.0)
        self.assertEqual(multiply(1e200, 1e200), float('inf'))
        self.assertEqual(multiply(1e200, -1e200), float('-inf'))

    def test_divide(self):
        """
        @brief Test the division function.
        @details Verifies quotient logic, precision with small numbers, and zero-division handling.
        """
        self.assertEqual(divide(10, 2), 5.0)
        self.assertEqual(divide(1e-308, 10), 1e-309)
        self.assertEqual(divide(1e308, 0.1), float('inf'))
        self.assertTrue(math.isnan(divide(10, 0)))

    def test_power(self):
        """
        @brief Test the power function.
        @details Verifies standard exponentiation, floating point bases, and handles OverflowError.
        """
        self.assertEqual(power(2, 3), 8.0)
        self.assertAlmostEqual(power(1.0000001, 10), 1.000001, places=5)
        try:
            res = power(10.0, 309.0)
            self.assertEqual(res, float('inf'))
        except OverflowError:
            pass   
        self.assertEqual(power(-2, 3), -8.0)

    def test_root(self):
        """
        @brief Test the n-th root function.
        @details Verifies standard roots, tiny value roots, and invalid inputs like negative values.
        """
        self.assertEqual(root(27, 3), 3.0)
        self.assertAlmostEqual(root(1e-20, 2), 1e-10)
        self.assertAlmostEqual(root(1e300, 100), 1e3)
        self.assertTrue(math.isnan(root(-10, 2)))

    def test_sqrt(self):
        """
        @brief Test the square root function.
        @details Verifies standard square roots, tiny values, large values, and invalid negative inputs.
        """
        self.assertEqual(sqrt(16.0), 4.0)                  
        self.assertAlmostEqual(sqrt(1e-20), 1e-10)         
        self.assertAlmostEqual(sqrt(1e300), 1e150)         
        self.assertTrue(math.isnan(sqrt(-1.0)))            
    
    def test_modulo(self):
        """
        @brief Test the modulo function.
        @details Verifies remainder for both integers and decimals, and division by zero handling.
        """
        self.assertEqual(modulo(10, 3), 1.0)
        self.assertAlmostEqual(modulo(0.000001, 0.0000007), 0.0000003)
        self.assertEqual(modulo(1e308, 3), 1e308 % 3)
        self.assertTrue(math.isnan(modulo(10, 0)))

    def test_pi(self):
        """
        @brief Test the pi constant function.
        @details Verifies that the function returns the correct mathematical constant pi.
        """
        self.assertEqual(pi(), math.pi)                    
        self.assertIsInstance(pi(), float)                 
        self.assertGreater(pi(), 3.14)                     
        self.assertLess(pi(), 3.15)
    
    
    def test_factorial(self):
        """@brief Test the factorial function with various inputs.
        @details Verifies standard factorial results, float-to-int conversion, 
        handling of invalid inputs.
        """
        self.assertEqual(factorial(0), 1.0)
        self.assertEqual(factorial(1), 1.0)
        self.assertEqual(factorial(5), 120.0)
        self.assertEqual(factorial(4.9), 24.0)
        self.assertTrue(math.isnan(factorial(-1)))
        self.assertTrue(math.isnan(factorial("text")))
        self.assertEqual(factorial(10000), math.inf)
        self.assertEqual(factorial(100000), math.inf)
    

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