"""
Tests for the NeuberCorrection class.
"""

# pylint: disable=wrong-import-position
import math
import os

import matplotlib
import matplotlib.pyplot as plt
import pytest

matplotlib.use("Agg")  # Use non-interactive backend

from neuber_correction import NeuberCorrection


class TestNeuberCorrection:
    """Test class for NeuberCorrection functionality."""

    def test_s355_steel_material_properties(self):
        """Test NeuberCorrection with S355 steel material properties."""
        # S355 steel properties from the image (80mm < t < 100mm)
        sigma_rp02 = 315  # MPa - yield strength
        sigma_u = 470  # MPa - tensile strength
        elastic_mod = 210000  # MPa - Young's modulus (210 GPa)
        epsilon_u = 0.12  # 12% strain at UTS

        neuber = NeuberCorrection(
            yield_strength=sigma_rp02,
            sigma_u=sigma_u,
            elastic_mod=elastic_mod,
            eps_u=epsilon_u,
        )

        # Verify material properties are stored correctly
        assert neuber.yield_strength == 315
        assert neuber.sigma_u == 470
        assert neuber.elastic_mod == 210000
        assert neuber.eps_u == 0.12
        assert neuber.tolerance == 1e-6
        assert neuber.max_iterations == 10000

    def test_ramberg_osgood_parameter_calculation(self):
        """Test the calculation of Ramberg-Osgood parameter n."""
        # S355 steel properties
        sigma_rp02 = 315  # MPa
        sigma_u = 470  # MPa
        elastic_mod = 210000  # MPa
        epsilon_u = 0.12  # 12%

        NeuberCorrection(
            yield_strength=sigma_rp02,
            sigma_u=sigma_u,
            elastic_mod=elastic_mod,
            eps_u=epsilon_u,
        )

        # Calculate n manually to verify the method
        elastic_strain_at_ultimate = sigma_u / elastic_mod
        plastic_strain_at_ultimate = epsilon_u - elastic_strain_at_ultimate
        expected_n = (
            math.log(plastic_strain_at_ultimate) - math.log(0.002)
        ) / math.log(sigma_u / sigma_rp02)

        # The expected value from the image is approximately 10.232
        assert abs(expected_n - 10.232) < 0.1

    def test_neuber_correction_specific_case(self):
        """Test Neuber correction for the specific case from the image."""
        # S355 steel properties
        sigma_rp02 = 315  # MPa
        sigma_u = 470  # MPa
        elastic_mod = 210000  # MPa
        epsilon_u = 0.12  # 12%

        neuber = NeuberCorrection(
            yield_strength=sigma_rp02,
            sigma_u=sigma_u,
            elastic_mod=elastic_mod,
            eps_u=epsilon_u,
        )

        # From the image: elastic stress σ_e = 718 MPa
        # The image shows the solution σ_p = 347.217 MPa
        elastic_stress = 718  # MPa

        # Test single stress correction
        corrected_stress = neuber.correct_stress_value(elastic_stress)

        # The corrected stress should be less than the elastic stress due to plasticity
        assert corrected_stress < elastic_stress

        # The corrected stress should be reasonable (not negative, not too small)
        assert corrected_stress > 0
        assert (
            corrected_stress > sigma_rp02 * 0.5
        )  # Should be reasonable fraction of yield

    def test_neuber_correction_convergence(self):
        """Test that the Neuber correction converges properly."""
        # S355 steel properties
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
            tolerance=1e-6,
            max_iterations=1000,
        )

        # Test with moderate stress levels
        stress_original = 400  # MPa

        corrected_stress = neuber.correct_stress_value(stress_original)

        # Verify convergence by checking that results are finite and reasonable
        assert math.isfinite(corrected_stress)
        assert corrected_stress > 0

    def test_neuber_correction_elastic_range(self):
        """Test Neuber correction when stress is in elastic range."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # Stress below yield should result in minimal correction
        stress_elastic = 200  # MPa (below yield)

        corrected_stress = neuber.correct_stress_value(stress_elastic)

        # Correction should be minimal for elastic stresses
        assert (
            abs(corrected_stress - stress_elastic) < 1.0
        )  # Less than 1 MPa difference

    def test_neuber_correction_high_stress(self):
        """Test Neuber correction with high stress levels."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # High stress levels should show significant correction
        stress_high = 800  # MPa (well above yield)

        corrected_stress = neuber.correct_stress_value(stress_high)

        # High stresses should show significant correction
        assert corrected_stress < stress_high
        assert (stress_high - corrected_stress) > 50  # Significant correction

    def test_neuber_correction_consistency(self):
        """Test that Neuber correction is consistent for same input."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        stress_input = 500

        # Run correction twice
        result1 = neuber.correct_stress_value(stress_input)
        result2 = neuber.correct_stress_value(stress_input)

        # Results should be identical
        assert result1 == result2

    def test_neuber_correction_parameter_validation(self):
        """Test that invalid parameters are handled properly."""
        # Test with zero values
        with pytest.raises(ValueError):
            NeuberCorrection(
                yield_strength=0,
                sigma_u=470,
                elastic_mod=210000,
                eps_u=0.12,  # Invalid
            )

        # Test with negative values
        with pytest.raises(ValueError):
            NeuberCorrection(
                yield_strength=315,
                sigma_u=-470,  # Invalid
                elastic_mod=210000,
                eps_u=0.12,
            )

    def test_neuber_correction_tolerance_effect(self):
        """Test the effect of different tolerance values."""
        # Test with different tolerances
        neuber_loose = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
            tolerance=1e-3,
        )

        neuber_tight = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
            tolerance=1e-9,
        )

        stress_input = 600

        result_loose = neuber_loose.correct_stress_value(stress_input)
        result_tight = neuber_tight.correct_stress_value(stress_input)

        # Results should be similar but not necessarily identical
        assert abs(result_loose - result_tight) < 3.0

    def test_neuber_correction_edge_cases(self):
        """Test edge cases for Neuber correction."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # Test with very small stresses
        stress_small = neuber.correct_stress_value(1.0)
        assert stress_small > 0

        # Test with very large stresses
        stress_large = neuber.correct_stress_value(2000)
        assert stress_large > 0
        assert stress_large < 2000

    def test_literature_values(self):
        """Test the Neuber correction with literature values."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
            tolerance=1e-6,
        )

        corrected_value = neuber.correct_stress_value(718)

        assert abs(corrected_value - 347.217) < 1

    def test_correct_stress_values_list(self):
        """Test the correct_stress_values method for processing lists."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # Test with a list of stress values
        stress_values = [400, 600, 800]
        corrected_values = neuber.correct_stress_values(stress_values)

        # Check that we get the same number of results
        assert len(corrected_values) == len(stress_values)

        # Check that each value is corrected (less than original due to plasticity)
        for original, corrected in zip(stress_values, corrected_values):
            assert corrected < original
            assert corrected > 0

        # Verify that individual calculations match list calculations
        individual_results = [
            neuber.correct_stress_value(stress) for stress in stress_values
        ]
        for list_result, individual_result in zip(corrected_values, individual_results):
            assert abs(list_result - individual_result) < 1e-10

    def test_multiple_stress_values_comprehensive(self):
        """Test comprehensive multiple stress value corrections."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # Test with various stress levels: elastic, near yield, and plastic
        stress_values = [200, 315, 400, 600, 800, 1000]
        corrected_values = neuber.correct_stress_values(stress_values)

        # Check that we get the same number of results
        assert len(corrected_values) == len(stress_values)

        # Check specific behaviors
        for original, corrected in zip(stress_values, corrected_values):
            # All corrected values should be positive
            assert corrected > 0

            # All corrected values should be less than or equal to original
            # (due to plasticity)
            assert corrected <= original

            # Elastic range (below yield): minimal correction
            if original <= 315:
                assert abs(corrected - original) < 2.0

            # Plastic range (above yield): significant correction
            if original > 315:
                assert (original - corrected) > 10  # Significant correction

        # Test that the results are consistent with individual calculations
        individual_results = [
            neuber.correct_stress_value(stress) for stress in stress_values
        ]

        for list_result, individual_result in zip(corrected_values, individual_results):
            assert abs(list_result - individual_result) < 1e-10

        # Test edge cases
        edge_stresses = [0.1, 5000]  # Very small and very large
        edge_corrected = neuber.correct_stress_values(edge_stresses)

        assert edge_corrected[0] > 0  # Very small stress
        assert edge_corrected[1] > 0 and edge_corrected[1] < 5000  # Very large stress

    def test_plot_neuber_diagram(self):
        """Test that the plotting function works without errors."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        # Test that plotting function returns figure and axis objects
        # Use show_plot=False to prevent display during testing
        fig, ax = neuber.plot_neuber_diagram(500, show_plot=False)

        # Verify that we got matplotlib objects back
        assert fig is not None
        assert ax is not None

        # Close the figure to free memory
        plt.close(fig)

    def test_plot_neuber_diagram_save_plot(self):
        """Test that the plotting function saves a file when save_plot=True."""
        neuber = NeuberCorrection(
            yield_strength=315,
            sigma_u=470,
            elastic_mod=210000,
            eps_u=0.12,
        )

        plot_name = "test_neuber_diagram_output"
        file_path = f"{plot_name}.png"

        # Ensure the file does not exist before the test
        if os.path.exists(file_path):
            os.remove(file_path)

        # Call the plotting function with save_plot=True
        fig, _ = neuber.plot_neuber_diagram(
            500, show_plot=False, save_plot=True, plot_name=plot_name
        )

        # Check that the file was created
        assert os.path.exists(file_path), f"Plot file {file_path} was not created."

        # Clean up the file after test
        os.remove(file_path)

        plt.close(fig)


if __name__ == "__main__":
    pytest.main([__file__])
