# AC-specific tests for Story 5-4: Vendor Model Bridge Example
# Tests edge cases, performance requirements, and acceptance criteria validation

import json
import threading
import time
from pathlib import Path
from typing import Any, Dict
from unittest.mock import MagicMock, Mock, patch
import tempfile

import pytest

from mca_sdk.integrations.bridge import VendorBridge


class DeltaVendorBridge(VendorBridge):
    """Bridge implementation for testing delta-to-cumulative conversion."""

    def __init__(self, *args, delta_sequence=None, **kwargs):
        """Initialize with a sequence of delta values to return."""
        self.delta_sequence = delta_sequence or []
        self.fetch_call_count = 0
        self.fetch_error = None
        self._cumulative_predictions = 0
        self._cumulative_errors = 0
        super().__init__(*args, **kwargs)

    def fetch_metrics(self) -> Dict[str, Any]:
        """Fetch metrics returning delta values from sequence."""
        if self.fetch_error:
            raise self.fetch_error

        if self.fetch_call_count < len(self.delta_sequence):
            data = self.delta_sequence[self.fetch_call_count]
        else:
            # Default after sequence exhausted
            data = {"metrics": {"predictions_delta": 0, "errors_delta": 0}}

        self.fetch_call_count += 1
        return data

    def transform_metrics(self, data: Dict[str, Any]) -> Dict[str, float]:
        """Transform vendor JSON to OTLP with delta-to-cumulative conversion."""
        metrics = data.get("metrics", {})

        # Extract delta values
        predictions_delta = metrics.get("predictions_delta", 0)
        errors_delta = metrics.get("errors_delta", 0)

        # Thread-safe cumulative conversion
        with self._lock:
            self._cumulative_predictions += predictions_delta
            self._cumulative_errors += errors_delta

            return {
                "vendor.predictions_total": self._cumulative_predictions,
                "vendor.errors_total": self._cumulative_errors,
                "vendor.accuracy_ratio": metrics.get("accuracy", 0),
            }


class TestDeltaToCumulativeConversion:
    """Test delta-to-cumulative conversion edge cases (AC 4)."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_normal_delta_accumulation(self, mock_client_class):
        """Test normal delta accumulation across multiple polls."""
        delta_sequence = [
            {"metrics": {"predictions_delta": 42, "errors_delta": 1, "accuracy": 0.95}},
            {"metrics": {"predictions_delta": 35, "errors_delta": 0, "accuracy": 0.96}},
            {"metrics": {"predictions_delta": 28, "errors_delta": 2, "accuracy": 0.94}},
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.05,
            max_iterations=3,
        )

        bridge.start()
        bridge.wait_for_completion()
        bridge.stop()

        # Verify cumulative totals: 42 + 35 + 28 = 105, 1 + 0 + 2 = 3
        assert bridge._cumulative_predictions == 105
        assert bridge._cumulative_errors == 3

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_zero_delta_values(self, mock_client_class):
        """Test handling of zero delta values (no new predictions/errors)."""
        delta_sequence = [
            {"metrics": {"predictions_delta": 10, "errors_delta": 1}},
            {"metrics": {"predictions_delta": 0, "errors_delta": 0}},  # No change
            {"metrics": {"predictions_delta": 5, "errors_delta": 0}},
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.05,
            max_iterations=3,
        )

        bridge.start()
        bridge.wait_for_completion()
        bridge.stop()

        # Cumulative should only include non-zero deltas: 10 + 0 + 5 = 15
        assert bridge._cumulative_predictions == 15
        assert bridge._cumulative_errors == 1

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_missing_delta_fields(self, mock_client_class):
        """Test graceful handling of missing delta fields in vendor response."""
        delta_sequence = [
            {"metrics": {"predictions_delta": 10, "errors_delta": 1}},
            {"metrics": {"accuracy": 0.95}},  # Missing delta fields
            {"metrics": {"predictions_delta": 5}},  # Missing errors_delta
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.05,
            max_iterations=3,
        )

        bridge.start()
        bridge.wait_for_completion()
        bridge.stop()

        # Should treat missing fields as 0: 10 + 0 + 5 = 15, 1 + 0 + 0 = 1
        assert bridge._cumulative_predictions == 15
        assert bridge._cumulative_errors == 1

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_first_poll_initialization(self, mock_client_class):
        """Test that first poll correctly initializes cumulative counters."""
        delta_sequence = [
            {"metrics": {"predictions_delta": 100, "errors_delta": 5}},
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.05,
            max_iterations=1,
        )

        # Verify starting from zero
        assert bridge._cumulative_predictions == 0
        assert bridge._cumulative_errors == 0

        bridge.start()
        bridge.wait_for_completion()
        bridge.stop()

        # First poll should initialize to delta value
        assert bridge._cumulative_predictions == 100
        assert bridge._cumulative_errors == 5


class TestPollingInterval:
    """Test polling interval accuracy (AC 2)."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_30_second_default_polling_interval(self, mock_client_class):
        """Verify default polling interval is 30 seconds as per AC."""
        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
        )

        # AC 2 requirement: default poll interval is 30 seconds
        assert bridge.poll_interval == 30.0

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_configurable_polling_interval(self, mock_client_class):
        """Verify polling interval is configurable."""
        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            poll_interval=60.0,
        )

        assert bridge.poll_interval == 60.0

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_actual_polling_timing(self, mock_client_class):
        """Test that actual polling occurs at specified intervals."""
        poll_interval = 0.1  # 100ms for fast testing
        delta_sequence = [
            {"metrics": {"predictions_delta": 1, "errors_delta": 0}},
            {"metrics": {"predictions_delta": 1, "errors_delta": 0}},
            {"metrics": {"predictions_delta": 1, "errors_delta": 0}},
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=poll_interval,
            max_iterations=3,
        )

        start_time = time.time()
        bridge.start()
        bridge.wait_for_completion()
        bridge.stop()
        elapsed = time.time() - start_time

        # Should take ~3 * 0.1 = 0.3 seconds (allow 50% tolerance for timing variance)
        expected = poll_interval * 3
        assert expected * 0.5 < elapsed < expected * 1.5
        assert bridge.fetch_call_count == 3


class TestResourceAttributes:
    """Test resource attribute extraction and consistency (AC 5)."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_vendor_name_resource_attribute(self, mock_client_class):
        """Verify vendor.name is passed to MCAClient."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        bridge = DeltaVendorBridge(
            service_name="test-service",
            vendor_name="mock-vendor",
            endpoint="http://localhost:8080",
        )

        # Verify MCAClient was initialized with vendor_name
        mock_client_class.assert_called_once()
        call_kwargs = mock_client_class.call_args[1]
        assert call_kwargs["vendor_name"] == "mock-vendor"

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_team_name_resource_attribute(self, mock_client_class):
        """Verify team.name is passed to MCAClient."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        bridge = DeltaVendorBridge(
            service_name="test-service",
            vendor_name="mock-vendor",
            endpoint="http://localhost:8080",
            team_name="integrations",
        )

        # Verify MCAClient was initialized with team_name
        call_kwargs = mock_client_class.call_args[1]
        assert call_kwargs["team_name"] == "integrations"

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_service_name_resource_attribute(self, mock_client_class):
        """Verify service.name is passed to MCAClient."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        bridge = DeltaVendorBridge(
            service_name="vendor-sepsis-v2",
            vendor_name="acme-healthcare",
            endpoint="http://localhost:8080",
        )

        # Verify MCAClient was initialized with service_name
        call_kwargs = mock_client_class.call_args[1]
        assert call_kwargs["service_name"] == "vendor-sepsis-v2"


class TestGracefulShutdown:
    """Test graceful shutdown without data loss (AC 6)."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_shutdown_during_polling(self, mock_client_class):
        """Test graceful shutdown while polling is active."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        delta_sequence = [
            {"metrics": {"predictions_delta": 10, "errors_delta": 1}},
            {"metrics": {"predictions_delta": 20, "errors_delta": 2}},
            {"metrics": {"predictions_delta": 30, "errors_delta": 3}},
        ]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.1,
            max_iterations=10,  # Long run
        )

        bridge.start()
        time.sleep(0.25)  # Allow ~2 polls
        bridge.stop()

        # Verify state is preserved (no data loss)
        # Should have completed at least 2 polls: 10 + 20 = 30, 1 + 2 = 3
        assert bridge._cumulative_predictions >= 30
        assert bridge._cumulative_errors >= 3
        assert not bridge.is_running()

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_shutdown_waits_for_poll_completion(self, mock_client_class):
        """Test that shutdown waits for current poll to complete."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        delta_sequence = [{"metrics": {"predictions_delta": 100, "errors_delta": 10}}]

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=delta_sequence,
            poll_interval=0.05,
            max_iterations=1,
        )

        bridge.start()
        time.sleep(0.02)  # Start poll but don't complete
        bridge.stop()  # Should wait for poll to finish

        # State should reflect completed poll
        assert bridge._cumulative_predictions == 100
        assert bridge._cumulative_errors == 10


class TestPerformanceRequirements:
    """Test performance requirements from Dev Notes."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_conversion_performance_under_50ms(self, mock_client_class):
        """Verify conversion logic completes in <50ms (AC requirement from Dev Notes)."""
        import time

        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        # Create bridge with complex metric data
        complex_data = {
            "metrics": {
                "predictions_delta": 1000,
                "errors_delta": 50,
                "accuracy": 0.95234,
                "f1_score": 0.88123,
                "latency_p50": 25.3,
                "latency_p90": 45.7,
                "latency_p99": 125.5,
            }
        }

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
            delta_sequence=[complex_data],
        )

        # Measure conversion time
        start_time = time.perf_counter()
        result = bridge.transform_metrics(complex_data)
        elapsed_ms = (time.perf_counter() - start_time) * 1000

        # AC requirement: conversion logic <50ms per polling cycle
        assert elapsed_ms < 50.0, f"Conversion took {elapsed_ms:.2f}ms, exceeds 50ms limit"
        assert result["vendor.predictions_total"] == 1000
        assert result["vendor.errors_total"] == 50

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_conversion_performance_100_iterations(self, mock_client_class):
        """Test conversion performance over 100 iterations."""
        import time

        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        data = {
            "metrics": {
                "predictions_delta": 10,
                "errors_delta": 1,
                "accuracy": 0.95,
            }
        }

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
        )

        # Run 100 conversions
        times = []
        for _ in range(100):
            start_time = time.perf_counter()
            bridge.transform_metrics(data)
            elapsed_ms = (time.perf_counter() - start_time) * 1000
            times.append(elapsed_ms)

        avg_time = sum(times) / len(times)
        max_time = max(times)
        p99_time = sorted(times)[98]  # 99th percentile

        # All iterations should be under 50ms
        assert avg_time < 50.0, f"Average conversion time {avg_time:.2f}ms exceeds 50ms"
        assert max_time < 50.0, f"Max conversion time {max_time:.2f}ms exceeds 50ms"
        assert p99_time < 50.0, f"P99 conversion time {p99_time:.2f}ms exceeds 50ms"


class TestThreadSafety:
    """Test thread safety of cumulative state updates."""

    @patch("mca_sdk.integrations.bridge.MCAClient")
    def test_concurrent_transform_calls(self, mock_client_class):
        """Test that concurrent transform_metrics calls are thread-safe."""
        mock_client = MagicMock()
        mock_client_class.return_value = mock_client

        bridge = DeltaVendorBridge(
            service_name="test",
            vendor_name="test",
            endpoint="http://localhost:8080",
        )

        data = {"metrics": {"predictions_delta": 1, "errors_delta": 0}}

        # Run 1000 concurrent transformations
        threads = []
        for _ in range(1000):
            t = threading.Thread(target=bridge.transform_metrics, args=(data,))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        # If thread-safe, cumulative should be exactly 1000
        # If not thread-safe, could be less due to race conditions
        assert bridge._cumulative_predictions == 1000
        assert bridge._cumulative_errors == 0
