defmodule ExMCP.ReliabilityIntegrationTest do
  @moduledoc """
  Integration tests for reliability features: retry policies, circuit breakers, and health monitoring.

  This test suite verifies that all reliability components work together correctly
  and handle various failure and recovery scenarios as expected.
  """

  use ExUnit.Case, async: true

  alias ExMCP.Client
  alias ExMCP.Reliability.Retry
  alias ExMCP.Transport.{ReliabilityWrapper, Test}

  describe "retry policies with circuit breakers" do
    test "retry policy respects circuit breaker state" do
      # Start a client with both retry and circuit breaker
      {:ok, client} =
        Client.start_link(
          transport: :test,
          _skip_connect: true,
          retry_policy: [
            max_attempts: 3,
            initial_delay: 10,
            max_delay: 100
          ],
          reliability: [
            circuit_breaker: [
              failure_threshold: 2,
              reset_timeout: 100
            ]
          ]
        )

      # Verify the client has both configured
      {:ok, retry_policy} = GenServer.call(client, :get_default_retry_policy)
      assert retry_policy[:max_attempts] == 3
    end

    test "circuit breaker prevents retry attempts when open" do
      # Create failing transport manually
      defmodule FailingTestTransport do
        @behaviour ExMCP.Transport

        def connect(_opts), do: {:ok, %{fail_count: 0}}
        def send_message(_msg, _state), do: {:error, :transport_failure}
        def receive_message(_state), do: {:error, :transport_failure}
        def close(_state), do: :ok
        def connected?(_state), do: false
      end

      # Wrap with reliability features
      {:ok, transport_state} = FailingTestTransport.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          FailingTestTransport,
          transport_state,
          circuit_breaker: [failure_threshold: 2, reset_timeout: 50]
        )

      # First two calls should fail and open the circuit
      assert {:error, :transport_failure} =
               ReliabilityWrapper.send_message("test1", wrapped_state)

      assert {:error, :transport_failure} =
               ReliabilityWrapper.send_message("test2", wrapped_state)

      # Third call should be rejected by circuit breaker
      assert {:error, :circuit_open} = ReliabilityWrapper.send_message("test3", wrapped_state)

      # Get circuit stats to verify it's open
      stats = ReliabilityWrapper.get_circuit_breaker_stats(wrapped_state)
      assert stats.state == :open

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end

  describe "health monitoring integration" do
    test "health check affects connection status" do
      # Create transport with health monitoring
      {:ok, transport_state} = Test.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          Test,
          transport_state,
          health_check: [check_interval: 50, failure_threshold: 1]
        )

      # Initial connection status should be based on health check
      # Wait for health check to initialize
      :timer.sleep(100)

      # Test transport returns false for connected? so health check will reflect that
      connected = ReliabilityWrapper.connected?(wrapped_state)
      assert is_boolean(connected)

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end

    test "health check with custom check function" do
      # Health check integration test - just verify the component works
      {:ok, transport_state} = Test.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          Test,
          transport_state,
          health_check: [
            check_interval: 100,
            failure_threshold: 1
          ]
        )

      # Wait for health check to initialize
      :timer.sleep(50)

      # Verify health check component is active
      health_status = ReliabilityWrapper.get_health_status(wrapped_state)
      assert health_status != nil

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end

  describe "combined failure scenarios" do
    test "circuit breaker protects against health check failures" do
      # This test simulates a scenario where the transport starts failing
      # and both health check and circuit breaker should respond

      defmodule DegradingTransport do
        @behaviour ExMCP.Transport

        def connect(_opts), do: {:ok, %{failure_mode: false}}

        def send_message(_msg, %{failure_mode: true} = _state) do
          {:error, :connection_error}
        end

        def send_message(_msg, state), do: {:ok, state}

        def receive_message(%{failure_mode: true} = _state), do: {:error, :connection_error}
        def receive_message(state), do: {:ok, "response", state}

        def close(_state), do: :ok
        def connected?(%{failure_mode: true}), do: false
        def connected?(_state), do: true

        # Method to trigger failure mode
        def trigger_failure(state), do: %{state | failure_mode: true}
      end

      {:ok, transport_state} = DegradingTransport.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          DegradingTransport,
          transport_state,
          circuit_breaker: [failure_threshold: 2, reset_timeout: 100],
          health_check: [check_interval: 50, failure_threshold: 1]
        )

      # Initially should work
      assert {:ok, _} = ReliabilityWrapper.send_message("test", wrapped_state)

      # Trigger failure mode in the underlying transport
      {DegradingTransport, degraded_state} = ReliabilityWrapper.unwrap(wrapped_state)
      failed_transport_state = DegradingTransport.trigger_failure(degraded_state)

      # Update wrapped state with failed transport
      failed_wrapped_state = %{wrapped_state | wrapped_state: failed_transport_state}

      # Now operations should start failing
      assert {:error, :connection_error} =
               ReliabilityWrapper.send_message("test", failed_wrapped_state)

      assert {:error, :connection_error} =
               ReliabilityWrapper.send_message("test", failed_wrapped_state)

      # Circuit should now be open
      assert {:error, :circuit_open} =
               ReliabilityWrapper.send_message("test", failed_wrapped_state)

      # Clean up
      ReliabilityWrapper.close(failed_wrapped_state)
    end
  end

  describe "recovery scenarios" do
    test "circuit breaker recovery after reset timeout" do
      # Test that circuit breaker properly transitions to half-open and then closed
      defmodule RecoveringTransport do
        @behaviour ExMCP.Transport

        def connect(_opts) do
          # Use an Agent to track state across calls
          {:ok, agent} = Agent.start_link(fn -> %{attempts: 0} end)
          {:ok, %{agent: agent}}
        end

        def send_message(_msg, %{agent: agent} = state) do
          attempts = Agent.get(agent, & &1.attempts)

          if attempts < 2 do
            {:error, :temporary_failure}
          else
            Agent.update(agent, fn s -> %{s | attempts: s.attempts + 1} end)
            {:ok, state}
          end
        end

        def receive_message(state), do: {:ok, "response", state}
        def close(%{agent: agent}), do: Agent.stop(agent)
        def connected?(_state), do: true
      end

      {:ok, transport_state} = RecoveringTransport.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          RecoveringTransport,
          transport_state,
          circuit_breaker: [failure_threshold: 2, reset_timeout: 50, success_threshold: 1]
        )

      # Fail twice to open circuit
      assert {:error, :temporary_failure} =
               ReliabilityWrapper.send_message("test1", wrapped_state)

      assert {:error, :temporary_failure} =
               ReliabilityWrapper.send_message("test2", wrapped_state)

      # Circuit should be open
      result = ReliabilityWrapper.send_message("test3", wrapped_state)
      assert {:error, :circuit_open} = result

      # Wait for reset timeout (need more time for half-open transition)
      :timer.sleep(150)

      # Update the Agent state to allow success
      Agent.update(transport_state.agent, fn s -> %{s | attempts: 2} end)

      # Next call should succeed and close the circuit
      assert {:ok, _} = ReliabilityWrapper.send_message("test4", wrapped_state)

      # Subsequent calls should work normally
      assert {:ok, _} = ReliabilityWrapper.send_message("test5", wrapped_state)

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end

  describe "configuration validation" do
    test "validates retry policy configuration" do
      # Verify policy merging works
      client_defaults = [max_attempts: 5, initial_delay: 50]
      operation_overrides = [max_attempts: 3, jitter: false]

      merged = Retry.merge_policies(client_defaults, operation_overrides)
      assert merged[:max_attempts] == 3
      assert merged[:initial_delay] == 50
      assert merged[:jitter] == false
    end

    test "validates circuit breaker configuration" do
      {:ok, transport_state} = Test.connect([])

      # Test with valid configuration
      assert {:ok, wrapped_state} =
               ReliabilityWrapper.wrap(
                 Test,
                 transport_state,
                 circuit_breaker: [failure_threshold: 5, reset_timeout: 1000]
               )

      # Verify circuit breaker is enabled
      assert wrapped_state.config.circuit_breaker_enabled == true
      assert is_pid(wrapped_state.circuit_breaker_pid)

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end

    test "disables components when set to false" do
      {:ok, transport_state} = Test.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          Test,
          transport_state,
          circuit_breaker: false,
          health_check: false
        )

      assert wrapped_state.config.circuit_breaker_enabled == false
      assert wrapped_state.config.health_check_enabled == false
      assert wrapped_state.circuit_breaker_pid == nil
      assert wrapped_state.health_check_pid == nil

      # Operations should work normally without reliability features
      assert {:ok, _} = ReliabilityWrapper.send_message("test", wrapped_state)

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end

  describe "error classification" do
    test "circuit breaker error classification affects circuit state" do
      # Test that different error types affect circuit breaker state appropriately
      defmodule ClassificationTestTransport do
        @behaviour ExMCP.Transport

        def connect(_opts), do: {:ok, %{error_type: :connection_error}}

        def send_message(_msg, %{error_type: error_type} = state) do
          case error_type do
            :connection_error -> {:error, {:connection_error, :refused}}
            :security_violation -> {:error, {:security_violation, :unauthorized}}
            :success -> {:ok, state}
          end
        end

        def receive_message(state), do: {:ok, "response", state}
        def close(_state), do: :ok
        def connected?(_state), do: true

        def set_error_type(state, error_type), do: %{state | error_type: error_type}
      end

      {:ok, transport_state} = ClassificationTestTransport.connect([])

      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          ClassificationTestTransport,
          transport_state,
          circuit_breaker: [failure_threshold: 2, reset_timeout: 100]
        )

      # Connection errors should count as failures and eventually open circuit
      assert {:error, {:connection_error, :refused}} =
               ReliabilityWrapper.send_message("test", wrapped_state)

      assert {:error, {:connection_error, :refused}} =
               ReliabilityWrapper.send_message("test", wrapped_state)

      # Circuit should now be open
      assert {:error, :circuit_open} = ReliabilityWrapper.send_message("test", wrapped_state)

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end

  describe "performance characteristics" do
    test "reliability features add acceptable overhead for successful operations" do
      {:ok, transport_state} = Test.connect([])

      # Test with reliability features enabled but relaxed thresholds
      {:ok, wrapped_state} =
        ReliabilityWrapper.wrap(
          Test,
          transport_state,
          # High threshold to avoid interference
          circuit_breaker: [failure_threshold: 100],
          # Very long interval
          health_check: [check_interval: 60000]
        )

      # Just verify operations work with reliability wrapper - performance overhead can vary
      for _ <- 1..10 do
        assert {:ok, _} = ReliabilityWrapper.send_message("test", wrapped_state)
      end

      # Clean up
      ReliabilityWrapper.close(wrapped_state)
    end
  end
end
