"""Unit tests for gfp_mcp.plot module."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from gfp_mcp.plot import (
    DB_FLOOR,
    HAS_MATPLOTLIB,
    _magnitude_squared_db,
    plot_s_parameters,
    render_simulation_plot,
)


class TestHasMatplotlib:
    """Test matplotlib availability flag."""

    def test_flag_exists(self) -> None:
        """Test that HAS_MATPLOTLIB flag is a boolean."""
        assert isinstance(HAS_MATPLOTLIB, bool)


class TestMagnitudeSquaredDb:
    """Test the dB conversion helper."""

    def test_basic_conversion(self) -> None:
        """Test basic magnitude squared to dB conversion."""
        result = _magnitude_squared_db([1.0], [0.0])
        assert len(result) == 1
        assert result[0] == pytest.approx(0.0)

    def test_complex_values(self) -> None:
        """Test conversion with complex S-parameter values."""
        result = _magnitude_squared_db([0.5], [0.5])
        expected = 10.0 * __import__("math").log10(0.5 * 0.5 + 0.5 * 0.5)
        assert result[0] == pytest.approx(expected)

    def test_zero_magnitude_returns_floor(self) -> None:
        """Test that zero magnitude returns DB_FLOOR instead of -inf."""
        result = _magnitude_squared_db([0.0], [0.0])
        assert result[0] == DB_FLOOR

    def test_negative_magnitude_squared(self) -> None:
        """Test negative magnitude squared case (shouldn't happen but handle gracefully)."""
        result = _magnitude_squared_db([0.0], [0.0])
        assert result[0] == DB_FLOOR

    def test_multiple_values(self) -> None:
        """Test conversion with multiple values."""
        result = _magnitude_squared_db([1.0, 0.5, 0.0], [0.0, 0.0, 0.0])
        assert len(result) == 3
        assert result[0] == pytest.approx(0.0)
        assert result[2] == DB_FLOOR


class TestPlotSParameters:
    """Test S-parameter plotting function."""

    def test_returns_none_without_matplotlib(self) -> None:
        """Test graceful degradation when matplotlib is unavailable."""
        with patch("gfp_mcp.plot.HAS_MATPLOTLIB", False):
            result = plot_s_parameters(
                wavelengths=[1.5, 1.55],
                sdict={"o1": {"o2": {"real": [0.5, 0.6], "imag": [0.0, 0.0]}}},
            )
            assert result is None

    def test_returns_none_for_empty_wavelengths(self) -> None:
        """Test returns None when wavelengths are empty."""
        result = plot_s_parameters(
            wavelengths=[],
            sdict={"o1": {"o2": {"real": [0.5], "imag": [0.0]}}},
        )
        assert result is None

    def test_returns_none_for_empty_sdict(self) -> None:
        """Test returns None when sdict is empty."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.55],
            sdict={},
        )
        assert result is None

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_returns_valid_png_bytes(self) -> None:
        """Test that valid data produces PNG bytes."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.52, 1.54, 1.56, 1.58, 1.6],
            sdict={
                "o1": {
                    "o2": {
                        "real": [0.1, 0.3, 0.7, 0.9, 0.7, 0.3],
                        "imag": [0.0, 0.1, 0.2, 0.1, 0.0, -0.1],
                    }
                }
            },
        )
        assert result is not None
        assert isinstance(result, bytes)
        assert result[:8] == b"\x89PNG\r\n\x1a\n"

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_multiple_port_pairs(self) -> None:
        """Test plotting with multiple S-parameter port pairs."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.55, 1.6],
            sdict={
                "o1": {
                    "o1": {"real": [0.9, 0.8, 0.7], "imag": [0.0, 0.0, 0.0]},
                    "o2": {"real": [0.1, 0.3, 0.5], "imag": [0.0, 0.0, 0.0]},
                },
                "o2": {
                    "o1": {"real": [0.1, 0.3, 0.5], "imag": [0.0, 0.0, 0.0]},
                    "o2": {"real": [0.9, 0.8, 0.7], "imag": [0.0, 0.0, 0.0]},
                },
            },
        )
        assert result is not None
        assert result[:8] == b"\x89PNG\r\n\x1a\n"

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_zero_magnitude_no_crash(self) -> None:
        """Test that zero-magnitude S-parameters don't cause log(0) crash."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.55, 1.6],
            sdict={"o1": {"o2": {"real": [0.0, 0.0, 0.0], "imag": [0.0, 0.0, 0.0]}}},
        )
        assert result is not None

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_mismatched_lengths_skipped(self) -> None:
        """Test that port pairs with mismatched lengths are skipped."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.55, 1.6],
            sdict={"o1": {"o2": {"real": [0.5, 0.6], "imag": [0.0, 0.0]}}},
        )
        assert result is not None

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_missing_imag_defaults_to_zero(self) -> None:
        """Test that missing imag component defaults to zero."""
        result = plot_s_parameters(
            wavelengths=[1.5, 1.55, 1.6],
            sdict={"o1": {"o2": {"real": [0.5, 0.6, 0.7]}}},
        )
        assert result is not None


class TestRenderSimulationPlot:
    """Test the high-level render function."""

    def test_returns_none_for_missing_wavelengths(self) -> None:
        """Test returns None when response lacks wavelengths."""
        result = render_simulation_plot(
            {"sdict": {"o1": {"o2": {"real": [0.5], "imag": [0.0]}}}},
            "test_component",
        )
        assert result is None

    def test_returns_none_for_missing_sdict(self) -> None:
        """Test returns None when response lacks sdict."""
        result = render_simulation_plot(
            {"wavelengths": [1.5, 1.55]},
            "test_component",
        )
        assert result is None

    def test_returns_none_for_empty_response(self) -> None:
        """Test returns None for empty response dict."""
        result = render_simulation_plot({}, "test_component")
        assert result is None

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_returns_image_content(self) -> None:
        """Test that valid data returns ImageContent."""
        result = render_simulation_plot(
            {
                "wavelengths": [1.5, 1.52, 1.54, 1.56, 1.58, 1.6],
                "sdict": {
                    "o1": {
                        "o2": {
                            "real": [0.1, 0.3, 0.7, 0.9, 0.7, 0.3],
                            "imag": [0.0, 0.1, 0.2, 0.1, 0.0, -0.1],
                        }
                    }
                },
            },
            "mzi",
        )
        assert result is not None
        assert result.type == "image"
        assert result.mimeType == "image/png"
        assert isinstance(result.data, str)
        assert len(result.data) > 0

    @pytest.mark.skipif(not HAS_MATPLOTLIB, reason="matplotlib not installed")
    def test_image_content_is_valid_base64_png(self) -> None:
        """Test that the base64 data decodes to valid PNG."""
        import base64

        result = render_simulation_plot(
            {
                "wavelengths": [1.5, 1.55, 1.6],
                "sdict": {
                    "o1": {
                        "o2": {
                            "real": [0.5, 0.6, 0.7],
                            "imag": [0.0, 0.0, 0.0],
                        }
                    }
                },
            },
            "straight",
        )
        assert result is not None
        png_bytes = base64.b64decode(result.data)
        assert png_bytes[:8] == b"\x89PNG\r\n\x1a\n"
