"""Unit tests for agentic AI tracking methods.

Tests cover:
- record_goal_started() and record_goal_completed()
- track_tool() context manager
- reasoning_step() context manager
- Error handling and metric recording
"""

import pytest
import time
from unittest.mock import Mock, MagicMock, patch

from mca_sdk.core.client import MCAClient
from mca_sdk.config.settings import MCAConfig


def make_client(**kwargs):
    """Create MCAClient with defaults suitable for testing."""
    defaults = {
        "service_name": "test-service",
        "model_id": "mdl-001",
        "team_name": "test-team",
        "model_category": "internal",
        "model_type": "agentic",
        "collector_endpoint": "http://localhost:4318",
        "strict_validation": False,  # Disable validation for unit tests
    }
    defaults.update(kwargs)
    return MCAClient(**defaults)


class TestReasoningStepErrorRecording:
    """Unit tests for reasoning_step() error recording."""

    @pytest.mark.error_scenario
    def test_reasoning_step_records_error_metric_on_exception(self):
        """Verify _reasoning_steps_counter records status: 'error' when exception occurs."""
        client = make_client()

        # Mock the reasoning steps counter to verify it's called
        mock_counter = MagicMock()
        client._reasoning_steps_counter = mock_counter

        # Execute reasoning_step that raises an exception
        with pytest.raises(ValueError, match="Test error"):
            with client.reasoning_step("planning", "Test planning step") as span:
                # Simulate work that fails
                raise ValueError("Test error")

        # Verify counter was called with status: "error"
        mock_counter.add.assert_called_once()
        call_args = mock_counter.add.call_args
        assert call_args[0][0] == 1  # increment by 1
        assert call_args[1]["attributes"]["status"] == "error"
        assert call_args[1]["attributes"]["step_type"] == "planning"

    def test_reasoning_step_records_success_metric_on_normal_completion(self):
        """Verify _reasoning_steps_counter records status: 'success' on normal completion."""
        client = make_client()

        # Mock the reasoning steps counter
        mock_counter = MagicMock()
        client._reasoning_steps_counter = mock_counter

        # Execute reasoning_step successfully
        with client.reasoning_step("evaluation", "Test evaluation") as span:
            # Simulate successful work
            pass

        # Verify counter was called with status: "success"
        mock_counter.add.assert_called_once()
        call_args = mock_counter.add.call_args
        assert call_args[0][0] == 1
        assert call_args[1]["attributes"]["status"] == "success"
        assert call_args[1]["attributes"]["step_type"] == "evaluation"

    def test_reasoning_step_exception_propagates(self):
        """Verify reasoning_step re-raises exceptions after recording metrics."""
        client = make_client()

        # Mock the counter
        mock_counter = MagicMock()
        client._reasoning_steps_counter = mock_counter

        # Execute reasoning_step with exception - it should propagate
        with pytest.raises(RuntimeError, match="Synthesis failed"):
            with client.reasoning_step("synthesis", "Test synthesis"):
                raise RuntimeError("Synthesis failed")

        # Verify counter was called before exception propagated
        mock_counter.add.assert_called_once()
        attrs = mock_counter.add.call_args[1]["attributes"]
        assert attrs["status"] == "error"


class TestGoalsFailedCounter:
    """Unit tests for agent.goals_failed_total counter."""

    def test_goals_failed_counter_incremented_on_failure(self):
        """Verify goals_failed_counter is incremented when goal fails."""
        client = make_client()

        # Mock counters
        mock_failed_counter = MagicMock()
        mock_completed_counter = MagicMock()
        client._goals_failed_counter = mock_failed_counter
        client._goals_completed_counter = mock_completed_counter

        # Start and fail a goal
        goal_id = client.record_goal_started("Test goal", "test")
        client.record_goal_completed(goal_id, status="failure")

        # Verify failed counter incremented, not completed counter
        mock_failed_counter.add.assert_called_once()
        mock_completed_counter.add.assert_not_called()

        # Verify attributes
        call_args = mock_failed_counter.add.call_args
        assert call_args[0][0] == 1
        assert call_args[1]["attributes"]["status"] == "failure"

    def test_goals_completed_counter_incremented_on_success(self):
        """Verify goals_completed_counter is incremented when goal succeeds."""
        client = make_client()

        # Mock counters
        mock_failed_counter = MagicMock()
        mock_completed_counter = MagicMock()
        client._goals_failed_counter = mock_failed_counter
        client._goals_completed_counter = mock_completed_counter

        # Start and complete a goal successfully
        goal_id = client.record_goal_started("Test goal", "test")
        client.record_goal_completed(goal_id, status="success")

        # Verify completed counter incremented, not failed counter
        mock_completed_counter.add.assert_called_once()
        mock_failed_counter.add.assert_not_called()

        # Verify attributes
        call_args = mock_completed_counter.add.call_args
        assert call_args[0][0] == 1
        assert call_args[1]["attributes"]["status"] == "success"

    def test_goals_completed_counter_incremented_on_partial(self):
        """Verify goals_completed_counter is incremented for partial completion."""
        client = make_client()

        # Mock counters
        mock_failed_counter = MagicMock()
        mock_completed_counter = MagicMock()
        client._goals_failed_counter = mock_failed_counter
        client._goals_completed_counter = mock_completed_counter

        # Start and partially complete a goal
        goal_id = client.record_goal_started("Test goal", "test")
        client.record_goal_completed(goal_id, status="partial")

        # Verify completed counter incremented (partial counts as completed)
        mock_completed_counter.add.assert_called_once()
        mock_failed_counter.add.assert_not_called()


class TestTrackToolLatencyRecording:
    """Unit tests for track_tool() latency recording."""

    def test_track_tool_records_latency_on_success(self):
        """Verify track_tool records latency histogram on successful completion."""
        client = make_client()

        # Mock histogram
        mock_histogram = MagicMock()
        client._tool_latency_histogram = mock_histogram

        # Execute tool successfully
        with client.track_tool("test_tool", goal_id="test-goal-123") as span:
            time.sleep(0.01)  # Small delay to ensure measurable latency

        # Verify histogram.record was called
        mock_histogram.record.assert_called_once()
        call_args = mock_histogram.record.call_args

        # Verify latency was recorded (should be > 0)
        recorded_latency = call_args[0][0]
        assert recorded_latency > 0

        # Verify attributes
        attrs = call_args[1]["attributes"]
        assert attrs["tool_name"] == "test_tool"
        assert attrs["goal_id"] == "test-goal-123"

    def test_track_tool_records_latency_on_exception(self):
        """Verify track_tool records latency even when exception occurs."""
        client = make_client()

        # Mock histogram and counter
        mock_histogram = MagicMock()
        mock_counter = MagicMock()
        client._tool_latency_histogram = mock_histogram
        client._tool_calls_counter = mock_counter

        # Execute tool with exception
        with pytest.raises(RuntimeError):
            with client.track_tool("failing_tool", goal_id="test-goal-456"):
                time.sleep(0.01)
                raise RuntimeError("Tool failed")

        # Verify histogram.record was called despite exception
        mock_histogram.record.assert_called_once()

        # Verify latency was recorded
        recorded_latency = mock_histogram.record.call_args[0][0]
        assert recorded_latency > 0
