"""Tests for the aggregation main loop signal propagation wiring.

Validates:
- Signal propagation is triggered after aggregation when competitive layer is enabled
- Consecutive failure tracking and operator alerting (Requirement 9.4)
- Propagation is skipped when competitive layer is disabled
"""
from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from services.aggregation.main import _trigger_signal_propagation
from services.shared.config import CompetitiveConfig


@pytest.fixture
def competitive_config():
    return CompetitiveConfig(
        propagation_failure_threshold=5,
    )


@pytest.fixture
def mock_pool():
    pool = AsyncMock()
    return pool


class TestTriggerSignalPropagation:
    """Tests for _trigger_signal_propagation."""

    @pytest.mark.asyncio
    async def test_no_records_returns_zero(self, mock_pool, competitive_config):
        """When no intelligence records exist, returns 0 signals."""
        mock_pool.fetch = AsyncMock(return_value=[])
        result = await _trigger_signal_propagation(mock_pool, "AAPL", competitive_config)
        assert result == 0

    @pytest.mark.asyncio
    async def test_skips_zero_impact_records(self, mock_pool, competitive_config):
        """Records with impact_score <= 0 are skipped."""
        mock_pool.fetch = AsyncMock(return_value=[
            {"document_id": "doc-1", "catalyst_type": "earnings", "impact_score": 0.0},
        ])
        with patch("services.aggregation.main.propagate_signals") as mock_prop:
            result = await _trigger_signal_propagation(mock_pool, "AAPL", competitive_config)
            assert result == 0
            mock_prop.assert_not_called()

    @pytest.mark.asyncio
    async def test_calls_propagate_signals_for_each_record(self, mock_pool, competitive_config):
        """propagate_signals is called for each valid intelligence record."""
        mock_pool.fetch = AsyncMock(return_value=[
            {"document_id": "doc-1", "catalyst_type": "earnings", "impact_score": 0.8},
            {"document_id": "doc-2", "catalyst_type": "m_and_a", "impact_score": 0.6},
        ])
        with patch("services.aggregation.main.propagate_signals", new_callable=AsyncMock) as mock_prop:
            mock_prop.return_value = []
            result = await _trigger_signal_propagation(mock_pool, "AAPL", competitive_config)
            assert mock_prop.call_count == 2
            # Verify correct args for first call
            call_args = mock_prop.call_args_list[0]
            assert call_args.kwargs["ticker"] == "AAPL"
            assert call_args.kwargs["catalyst_type"] == "earnings"
            assert call_args.kwargs["impact_score"] == 0.8
            assert call_args.kwargs["document_id"] == "doc-1"

    @pytest.mark.asyncio
    async def test_returns_total_signal_count(self, mock_pool, competitive_config):
        """Returns the total number of competitive signals produced."""
        mock_pool.fetch = AsyncMock(return_value=[
            {"document_id": "doc-1", "catalyst_type": "earnings", "impact_score": 0.8},
            {"document_id": "doc-2", "catalyst_type": "m_and_a", "impact_score": 0.6},
        ])
        mock_record = MagicMock()
        with patch("services.aggregation.main.propagate_signals", new_callable=AsyncMock) as mock_prop:
            mock_prop.side_effect = [
                [mock_record, mock_record],  # 2 signals from first doc
                [mock_record],               # 1 signal from second doc
            ]
            result = await _trigger_signal_propagation(mock_pool, "AAPL", competitive_config)
            assert result == 3

    @pytest.mark.asyncio
    async def test_consecutive_failure_tracking(self, mock_pool, competitive_config):
        """After threshold consecutive failures, logs critical alert and stops."""
        import services.aggregation.main as main_mod
        # Reset the global counter
        main_mod._propagation_consecutive_failures = 0

        cfg = CompetitiveConfig(propagation_failure_threshold=3)
        mock_pool.fetch = AsyncMock(return_value=[
            {"document_id": f"doc-{i}", "catalyst_type": "earnings", "impact_score": 0.8}
            for i in range(5)
        ])
        with patch("services.aggregation.main.propagate_signals", new_callable=AsyncMock) as mock_prop:
            mock_prop.side_effect = RuntimeError("DB connection lost")
            result = await _trigger_signal_propagation(mock_pool, "AAPL", cfg)
            # Should stop after 3 failures (threshold)
            assert mock_prop.call_count == 3
            assert main_mod._propagation_consecutive_failures == 3
            assert result == 0

        # Reset for other tests
        main_mod._propagation_consecutive_failures = 0

    @pytest.mark.asyncio
    async def test_success_resets_failure_counter(self, mock_pool, competitive_config):
        """A successful propagation resets the consecutive failure counter."""
        import services.aggregation.main as main_mod
        main_mod._propagation_consecutive_failures = 4  # Near threshold

        mock_pool.fetch = AsyncMock(return_value=[
            {"document_id": "doc-1", "catalyst_type": "earnings", "impact_score": 0.8},
        ])
        with patch("services.aggregation.main.propagate_signals", new_callable=AsyncMock) as mock_prop:
            mock_prop.return_value = []
            await _trigger_signal_propagation(mock_pool, "AAPL", competitive_config)
            assert main_mod._propagation_consecutive_failures == 0

        # Reset for other tests
        main_mod._propagation_consecutive_failures = 0
