defmodule SnmpKit.SnmpMgr.ErrorsTest do
  use ExUnit.Case, async: true

  alias SnmpKit.SnmpMgr.Errors

  describe "code_to_atom/1" do
    test "converts standard SNMP error codes to atoms" do
      assert Errors.code_to_atom(0) == :no_error
      assert Errors.code_to_atom(1) == :too_big
      assert Errors.code_to_atom(2) == :no_such_name
      assert Errors.code_to_atom(3) == :bad_value
      assert Errors.code_to_atom(4) == :read_only
      assert Errors.code_to_atom(5) == :gen_err
    end

    test "converts SNMPv2c error codes to atoms" do
      assert Errors.code_to_atom(6) == :no_access
      assert Errors.code_to_atom(7) == :wrong_type
      assert Errors.code_to_atom(8) == :wrong_length
      assert Errors.code_to_atom(9) == :wrong_encoding
      assert Errors.code_to_atom(10) == :wrong_value
      assert Errors.code_to_atom(11) == :no_creation
      assert Errors.code_to_atom(12) == :inconsistent_value
      assert Errors.code_to_atom(13) == :resource_unavailable
      assert Errors.code_to_atom(14) == :commit_failed
      assert Errors.code_to_atom(15) == :undo_failed
      assert Errors.code_to_atom(16) == :authorization_error
      assert Errors.code_to_atom(17) == :not_writable
      assert Errors.code_to_atom(18) == :inconsistent_name
    end

    test "returns :unknown_error for invalid codes" do
      assert Errors.code_to_atom(999) == :unknown_error
      assert Errors.code_to_atom(-1) == :unknown_error
    end
  end

  describe "description/1" do
    test "provides descriptions for standard errors" do
      assert Errors.description(:no_error) == "No error occurred"
      assert Errors.description(:too_big) == "Response too big to fit in message"
      assert Errors.description(:no_such_name) == "Variable name not found"
      assert Errors.description(:bad_value) == "Invalid value for variable"
      assert Errors.description(:read_only) == "Variable is read-only"
      assert Errors.description(:gen_err) == "General error"
    end

    test "provides descriptions for SNMPv2c errors" do
      assert Errors.description(:no_access) == "Access denied"
      assert Errors.description(:wrong_type) == "Wrong data type"
      assert Errors.description(:authorization_error) == "Authorization failed"
      assert Errors.description(:not_writable) == "Variable not writable"
    end

    test "returns 'Unknown error' for unknown atoms" do
      assert Errors.description(:unknown_error) == "Unknown error"
      assert Errors.description(:some_random_atom) == "Unknown error"
    end
  end

  describe "code_to_description/1" do
    test "converts code directly to description" do
      assert Errors.code_to_description(2) == "Variable name not found"
      assert Errors.code_to_description(18) == "Inconsistent name"
      assert Errors.code_to_description(5) == "General error"
    end

    test "handles unknown codes" do
      assert Errors.code_to_description(999) == "Unknown error"
    end
  end

  describe "v2c_error?/1" do
    test "returns true for SNMPv2c-specific errors" do
      assert Errors.v2c_error?(:no_access) == true
      assert Errors.v2c_error?(:wrong_type) == true
      assert Errors.v2c_error?(:wrong_length) == true
      assert Errors.v2c_error?(:authorization_error) == true
      assert Errors.v2c_error?(:not_writable) == true
    end

    test "returns false for SNMPv1 errors" do
      assert Errors.v2c_error?(:no_such_name) == false
      assert Errors.v2c_error?(:too_big) == false
      assert Errors.v2c_error?(:bad_value) == false
      assert Errors.v2c_error?(:read_only) == false
      assert Errors.v2c_error?(:gen_err) == false
    end

    test "returns false for unknown errors" do
      assert Errors.v2c_error?(:unknown_error) == false
      assert Errors.v2c_error?(:timeout) == false
    end
  end

  describe "format_error/1" do
    test "formats SNMP errors with integer codes" do
      result = Errors.format_error({:snmp_error, 2})
      assert result == "SNMP Error (2): Variable name not found"
    end

    test "formats SNMP errors with atom codes" do
      result = Errors.format_error({:snmp_error, :no_such_name})
      assert result == "SNMP Error: Variable name not found"
    end

    test "formats SNMPv2c errors without details" do
      result = Errors.format_error({:v2c_error, :no_access})
      assert result == "SNMPv2c Error: Access denied"
    end

    test "formats SNMPv2c errors with details" do
      result = Errors.format_error({:v2c_error, :no_access, oid: "1.2.3.4"})
      assert result == "SNMPv2c Error: Access denied (OID: 1.2.3.4)"
    end

    test "formats SNMPv2c errors with multiple details" do
      result = Errors.format_error({:v2c_error, :wrong_type, [oid: "1.2.3.4", value: "test"]})
      assert result =~ "SNMPv2c Error: Wrong data type"
      assert result =~ "OID: 1.2.3.4"
      assert result =~ "Value: \"test\""
    end

    test "formats network errors" do
      result = Errors.format_error({:network_error, :timeout})
      assert result == "Network Error: :timeout"
    end

    test "formats timeout errors" do
      result = Errors.format_error({:timeout, nil})
      assert result == "Timeout Error: Request timed out"
    end

    test "formats encoding errors" do
      result = Errors.format_error({:encoding_error, "bad data"})
      assert result == "Encoding Error: \"bad data\""
    end

    test "formats decoding errors" do
      result = Errors.format_error({:decoding_error, "corrupt packet"})
      assert result == "Decoding Error: \"corrupt packet\""
    end

    test "formats unknown errors" do
      result = Errors.format_error(:some_random_error)
      assert result == "Unknown Error: :some_random_error"
    end
  end

  describe "recoverable?/1" do
    test "returns false for unrecoverable network errors" do
      assert Errors.recoverable?({:network_error, :host_unreachable}) == false
      assert Errors.recoverable?({:network_error, :network_unreachable}) == false
    end

    test "returns false for permanent SNMP errors" do
      assert Errors.recoverable?({:snmp_error, :no_such_name}) == false
      assert Errors.recoverable?({:snmp_error, :bad_value}) == false
      assert Errors.recoverable?({:snmp_error, :read_only}) == false
    end

    test "returns false for permanent SNMPv2c errors" do
      assert Errors.recoverable?({:v2c_error, :no_access}) == false
      assert Errors.recoverable?({:v2c_error, :not_writable}) == false
      assert Errors.recoverable?({:v2c_error, :wrong_type}) == false
    end

    test "returns true for recoverable errors" do
      assert Errors.recoverable?(:timeout) == true
      assert Errors.recoverable?({:snmp_error, :too_big}) == true
      assert Errors.recoverable?({:snmp_error, :gen_err}) == true
    end

    test "returns false for unknown errors" do
      assert Errors.recoverable?(:unknown_error) == false
      assert Errors.recoverable?({:custom_error, "test"}) == false
    end
  end

  describe "analyze_error/1" do
    test "analyzes SNMP protocol errors with integer codes" do
      result = Errors.analyze_error({:snmp_error, 2})

      assert result.type == :snmp_protocol
      assert result.atom == :no_such_name
      assert result.code == 2
      assert result.severity == :error
      assert result.retriable == false
      assert result.category == :user_error
      assert result.description == "Variable name not found"
    end

    test "analyzes SNMP protocol errors with atom codes" do
      result = Errors.analyze_error({:snmp_error, :too_big})

      assert result.type == :snmp_protocol
      assert result.atom == :too_big
      assert result.code == 1
      assert result.retriable == true
      assert result.category == :resource_error
    end

    test "analyzes network timeout errors" do
      result = Errors.analyze_error(:timeout)

      assert result.type == :network
      assert result.atom == :timeout
      assert result.retriable == true
      assert result.category == :transient_error
      assert result.description == "Operation timed out"
    end

    test "analyzes host unreachable errors" do
      result = Errors.analyze_error(:host_unreachable)

      assert result.type == :network
      assert result.atom == :host_unreachable
      assert result.retriable == false
      assert result.category == :configuration_error
    end

    test "analyzes connection refused errors" do
      result = Errors.analyze_error(:connection_refused)

      assert result.type == :network
      assert result.retriable == false
      assert result.category == :service_error
    end

    test "analyzes unknown errors" do
      result = Errors.analyze_error({:custom_error, "test"})

      assert result.type == :other
      assert result.retriable == false
      # classify_error returns a tuple for unknown errors, but as subcategory
      assert result.category == :unknown_error
      assert result.subcategory == {:custom_error, "test"}
    end
  end

  describe "classify_error/2" do
    test "classifies user errors" do
      assert Errors.classify_error({:snmp_error, :no_such_name}) == {:user_error, :invalid_oid}
      assert Errors.classify_error({:snmp_error, :bad_value}) == {:user_error, :invalid_value}
      assert Errors.classify_error({:snmp_error, :read_only}) == {:user_error, :write_to_readonly}
      assert Errors.classify_error({:v2c_error, :wrong_type}) == {:user_error, :type_mismatch}
    end

    test "classifies security errors" do
      assert Errors.classify_error({:v2c_error, :no_access}) == {:security_error, :access_denied}

      assert Errors.classify_error({:v2c_error, :authorization_error}) ==
               {:security_error, :authorization_failed}
    end

    test "classifies device errors" do
      assert Errors.classify_error({:snmp_error, :gen_err}) == {:device_error, :general_failure}

      assert Errors.classify_error({:v2c_error, :resource_unavailable}) ==
               {:device_error, :resource_exhausted}
    end

    test "classifies recoverable errors" do
      assert Errors.classify_error({:snmp_error, :too_big}) ==
               {:recoverable_error, :response_too_large}
    end

    test "classifies network errors" do
      assert Errors.classify_error({:network_error, :timeout}) ==
               {:transient_error, :network_timeout}

      assert Errors.classify_error({:network_error, :host_unreachable}) ==
               {:configuration_error, :unreachable_host}

      assert Errors.classify_error({:network_error, :network_unreachable}) ==
               {:configuration_error, :network_unavailable}
    end

    test "classifies protocol errors" do
      assert Errors.classify_error({:encoding_error, "bad data"}) ==
               {:protocol_error, :message_encoding_failed}

      assert Errors.classify_error({:decoding_error, "corrupt"}) ==
               {:protocol_error, :message_decoding_failed}
    end

    test "classifies validation errors" do
      assert Errors.classify_error(:invalid_oid_values) == :validation_error
      assert Errors.classify_error({:snmp_encoding_error, "test"}) == :validation_error
      assert Errors.classify_error(:encoding_failed) == :validation_error
    end

    test "classifies authentication errors" do
      assert Errors.classify_error(:authentication_error) == :authentication_error
      assert Errors.classify_error(:invalid_community) == :invalid_community
      assert Errors.classify_error(:bad_community) == :invalid_community
    end

    test "classifies system errors" do
      assert Errors.classify_error(:snmp_modules_not_available) == :system_error
    end

    test "classifies timeout errors" do
      assert Errors.classify_error(:timeout) == {:transient_error, :operation_timeout}
    end

    test "accepts optional context parameter" do
      # Context doesn't change behavior but is accepted
      assert Errors.classify_error({:snmp_error, :no_such_name}, "Get request") ==
               {:user_error, :invalid_oid}
    end

    test "classifies unknown errors" do
      assert Errors.classify_error(:unknown_atom) == {:unknown_error, :unknown_atom}
      assert Errors.classify_error({:custom, "test"}) == {:unknown_error, {:custom, "test"}}
    end
  end

  describe "format_user_friendly_error/2" do
    test "formats SNMP errors with context" do
      assert Errors.format_user_friendly_error({:snmp_error, :no_such_name}, "Getting sysDescr") ==
               "Getting sysDescr failed: The requested OID does not exist on the device"

      assert Errors.format_user_friendly_error({:snmp_error, :bad_value}, "Setting value") ==
               "Setting value failed: The provided value is invalid for this OID"
    end

    test "formats network errors with context" do
      assert Errors.format_user_friendly_error({:network_error, :timeout}, "Polling device") ==
               "Polling device failed: The device did not respond within the timeout period"

      assert Errors.format_user_friendly_error(
               {:network_error, :host_unreachable},
               "Connecting"
             ) ==
               "Connecting failed: Cannot reach the device. Check the IP address and network connectivity"
    end

    test "formats timeout errors" do
      assert Errors.format_user_friendly_error(:timeout, "Discovery") ==
               "Discovery timed out: The operation took too long to complete"
    end

    test "uses default context when not provided" do
      assert Errors.format_user_friendly_error({:snmp_error, :no_such_name}) ==
               "Operation failed: The requested OID does not exist on the device"
    end

    test "formats encoding/decoding errors" do
      assert Errors.format_user_friendly_error({:encoding_error, "test"}, "Sending request") ==
               "Sending request failed: Unable to encode the SNMP message"

      assert Errors.format_user_friendly_error({:decoding_error, "test"}, "Reading response") ==
               "Reading response failed: Unable to decode the SNMP response"
    end

    test "formats unknown errors" do
      result = Errors.format_user_friendly_error({:custom_error, "test"}, "Testing")
      assert result =~ "Testing failed:"
    end
  end

  describe "get_recovery_suggestions/1" do
    test "provides suggestions for no_such_name errors" do
      suggestions = Errors.get_recovery_suggestions({:snmp_error, :no_such_name})

      assert "Verify the OID is correct" in suggestions
      assert "Check if the OID is supported by this device" in suggestions
      assert "Try using MIB browser to explore available OIDs" in suggestions
      assert "Ensure you're using the correct SNMP version" in suggestions
    end

    test "provides suggestions for bad_value errors" do
      suggestions = Errors.get_recovery_suggestions({:snmp_error, :bad_value})

      assert "Check the value format and type" in suggestions
      assert "Verify the value is within acceptable range" in suggestions
    end

    test "provides suggestions for read_only errors" do
      suggestions = Errors.get_recovery_suggestions({:snmp_error, :read_only})

      assert "This OID cannot be modified" in suggestions
      assert "Use GET operation instead of SET" in suggestions
    end

    test "provides suggestions for too_big errors" do
      suggestions = Errors.get_recovery_suggestions({:snmp_error, :too_big})

      assert "Reduce the number of OIDs in the request" in suggestions
      assert "Use GETBULK operation with smaller max-repetitions" in suggestions
      assert "Split the request into multiple smaller requests" in suggestions
    end

    test "provides suggestions for access errors" do
      suggestions = Errors.get_recovery_suggestions({:v2c_error, :no_access})

      assert "Verify the community string is correct" in suggestions
      assert "Check device SNMP access configuration" in suggestions
    end

    test "provides suggestions for timeout errors" do
      suggestions = Errors.get_recovery_suggestions({:network_error, :timeout})

      assert "Increase timeout value" in suggestions
      assert "Check network connectivity to the device" in suggestions
      assert "Verify device is responding to ping" in suggestions
    end

    test "provides suggestions for host unreachable errors" do
      suggestions = Errors.get_recovery_suggestions({:network_error, :host_unreachable})

      assert "Verify the IP address is correct" in suggestions
      assert "Check network routing" in suggestions
      assert "Test basic connectivity with ping" in suggestions
    end

    test "provides suggestions for encoding errors" do
      suggestions = Errors.get_recovery_suggestions({:encoding_error, "test"})

      assert "Check OID format" in suggestions
      assert "Verify value types are supported" in suggestions
    end

    test "provides suggestions for decoding errors" do
      suggestions = Errors.get_recovery_suggestions({:decoding_error, "test"})

      assert "Device may not support SNMP" in suggestions
      assert "Check SNMP version compatibility" in suggestions
    end

    test "provides suggestions for timeout atom" do
      suggestions = Errors.get_recovery_suggestions(:timeout)

      assert "Increase operation timeout" in suggestions
      assert "Check if operation is too complex" in suggestions
    end

    test "provides generic suggestions for unknown errors" do
      suggestions = Errors.get_recovery_suggestions({:custom_error, "test"})

      assert "Check device documentation" in suggestions
      assert "Verify SNMP configuration" in suggestions
      assert "Contact device vendor support" in suggestions
    end
  end
end
