"""
Unit tests for ImpactAnalyzer and metrics functions.

Tests diff ratios, magnitude scoring, and threshold decisions.
"""

import pytest

from markitect.prompts.incremental.metrics import (
    structural_diff_ratio,
    line_diff_ratio,
    calculate_change_magnitude,
)
from markitect.prompts.incremental.impact import ImpactAnalyzer
from markitect.prompts.incremental.models import RecomputeConfig


class TestStructuralDiffRatio:
    """Tests for structural_diff_ratio."""

    def test_identical_content(self):
        """Test identical content returns 0.0."""
        assert structural_diff_ratio("hello", "hello") == 0.0

    def test_completely_different(self):
        """Test completely different content returns high ratio."""
        ratio = structural_diff_ratio("aaa", "zzz")
        assert ratio > 0.5

    def test_empty_strings(self):
        """Test both empty returns 0.0."""
        assert structural_diff_ratio("", "") == 0.0

    def test_one_empty(self):
        """Test one empty returns 1.0."""
        assert structural_diff_ratio("", "content") == 1.0
        assert structural_diff_ratio("content", "") == 1.0

    def test_small_change(self):
        """Test small change returns low ratio."""
        old = "The quick brown fox jumps over the lazy dog"
        new = "The quick brown fox leaps over the lazy dog"
        ratio = structural_diff_ratio(old, new)
        assert 0.0 < ratio < 0.5

    def test_returns_float(self):
        """Test return value is float between 0 and 1."""
        ratio = structural_diff_ratio("abc", "abd")
        assert isinstance(ratio, float)
        assert 0.0 <= ratio <= 1.0


class TestLineDiffRatio:
    """Tests for line_diff_ratio."""

    def test_identical_lines(self):
        """Test identical multi-line content returns 0.0."""
        content = "line1\nline2\nline3"
        assert line_diff_ratio(content, content) == 0.0

    def test_one_line_changed(self):
        """Test changing one line of several."""
        old = "line1\nline2\nline3"
        new = "line1\nmodified\nline3"
        ratio = line_diff_ratio(old, new)
        assert 0.0 < ratio < 1.0

    def test_all_lines_changed(self):
        """Test all lines changed returns high ratio."""
        old = "aaa\nbbb\nccc"
        new = "xxx\nyyy\nzzz"
        ratio = line_diff_ratio(old, new)
        assert ratio > 0.5

    def test_empty_strings(self):
        """Test both empty returns 0.0."""
        assert line_diff_ratio("", "") == 0.0

    def test_one_empty(self):
        """Test one empty returns 1.0."""
        assert line_diff_ratio("", "content") == 1.0
        assert line_diff_ratio("content", "") == 1.0


class TestCalculateChangeMagnitude:
    """Tests for calculate_change_magnitude."""

    def test_none_old_content(self):
        """Test None old_content (creation) returns 1.0."""
        assert calculate_change_magnitude(None, "new content") == 1.0

    def test_none_new_content(self):
        """Test None new_content (deletion) returns 1.0."""
        assert calculate_change_magnitude("old content", None) == 1.0

    def test_both_none(self):
        """Test both None returns 0.0."""
        assert calculate_change_magnitude(None, None) == 0.0

    def test_structural_method(self):
        """Test structural method (default)."""
        result = calculate_change_magnitude("abc", "abd", method="structural")
        assert 0.0 < result < 1.0

    def test_line_method(self):
        """Test line method."""
        result = calculate_change_magnitude("abc\ndef", "abc\nxyz", method="line")
        assert 0.0 < result < 1.0

    def test_identical_content(self):
        """Test identical content returns 0.0."""
        assert calculate_change_magnitude("same", "same") == 0.0


class TestImpactAnalyzer:
    """Tests for ImpactAnalyzer class."""

    @pytest.fixture
    def analyzer(self):
        """Create ImpactAnalyzer instance."""
        return ImpactAnalyzer()

    def test_calculate_magnitude(self, analyzer):
        """Test magnitude calculation delegates to metrics."""
        result = analyzer.calculate_magnitude("old", "new")
        assert isinstance(result, float)
        assert 0.0 <= result <= 1.0

    def test_calculate_magnitude_creation(self, analyzer):
        """Test magnitude for creation."""
        assert analyzer.calculate_magnitude(None, "new") == 1.0

    def test_calculate_magnitude_identical(self, analyzer):
        """Test magnitude for identical content."""
        assert analyzer.calculate_magnitude("same", "same") == 0.0

    def test_should_recompute_above_threshold(self, analyzer):
        """Test recompute when magnitude exceeds threshold."""
        config = RecomputeConfig(impact_threshold=0.3)
        assert analyzer.should_recompute(0.5, config) is True

    def test_should_recompute_at_threshold(self, analyzer):
        """Test recompute when magnitude equals threshold."""
        config = RecomputeConfig(impact_threshold=0.5)
        assert analyzer.should_recompute(0.5, config) is True

    def test_should_not_recompute_below_threshold(self, analyzer):
        """Test no recompute when magnitude below threshold."""
        config = RecomputeConfig(impact_threshold=0.5)
        assert analyzer.should_recompute(0.3, config) is False

    def test_zero_threshold_always_recomputes(self, analyzer):
        """Test zero threshold means any change triggers recompute."""
        config = RecomputeConfig(impact_threshold=0.0)
        assert analyzer.should_recompute(0.0, config) is True
        assert analyzer.should_recompute(0.01, config) is True

    def test_high_threshold_only_major_changes(self, analyzer):
        """Test high threshold only triggers on major changes."""
        config = RecomputeConfig(impact_threshold=0.9)
        assert analyzer.should_recompute(0.5, config) is False
        assert analyzer.should_recompute(0.95, config) is True
