defmodule SnmpKit.SnmpMgr.Errors do
  @moduledoc """
  SNMP error handling and error code translation.

  Provides functions to handle both SNMPv1 and SNMPv2c error conditions
  and translate error codes to human-readable messages.
  """
  alias SnmpKit.SnmpLib.Error

  # SNMPv1 and SNMPv2c error codes
  @error_codes %{
    0 => :no_error,
    1 => :too_big,
    2 => :no_such_name,
    3 => :bad_value,
    4 => :read_only,
    5 => :gen_err,

    # SNMPv2c additional error codes
    6 => :no_access,
    7 => :wrong_type,
    8 => :wrong_length,
    9 => :wrong_encoding,
    10 => :wrong_value,
    11 => :no_creation,
    12 => :inconsistent_value,
    13 => :resource_unavailable,
    14 => :commit_failed,
    15 => :undo_failed,
    16 => :authorization_error,
    17 => :not_writable,
    18 => :inconsistent_name
  }

  @error_descriptions %{
    :no_error => "No error occurred",
    :too_big => "Response too big to fit in message",
    :no_such_name => "Variable name not found",
    :bad_value => "Invalid value for variable",
    :read_only => "Variable is read-only",
    :gen_err => "General error",

    # SNMPv2c additional errors
    :no_access => "Access denied",
    :wrong_type => "Wrong data type",
    :wrong_length => "Wrong data length",
    :wrong_encoding => "Wrong encoding",
    :wrong_value => "Wrong value",
    :no_creation => "Cannot create variable",
    :inconsistent_value => "Inconsistent value",
    :resource_unavailable => "Resource unavailable",
    :commit_failed => "Commit failed",
    :undo_failed => "Undo failed",
    :authorization_error => "Authorization failed",
    :not_writable => "Variable not writable",
    :inconsistent_name => "Inconsistent name"
  }

  @doc """
  Translates an SNMP error code to an atom.

  Uses SnmpKit.SnmpLib.Error for validation and standardization of RFC-compliant error codes.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.code_to_atom(2)
      :no_such_name

      iex> SnmpKit.SnmpMgr.Errors.code_to_atom(0)
      :no_error

      iex> SnmpKit.SnmpMgr.Errors.code_to_atom(999)
      :unknown_error
  """
  def code_to_atom(error_code) when is_integer(error_code) do
    # Use SnmpKit.SnmpLib.Error for validation and RFC compliance, fall back to our mapping
    if Error.valid_error_status?(error_code) do
      Error.error_atom(error_code)
    else
      Map.get(@error_codes, error_code, :unknown_error)
    end
  end

  @doc """
  Translates an SNMP error atom to a human-readable description.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.description(:no_such_name)
      "Variable name not found"

      iex> SnmpKit.SnmpMgr.Errors.description(:too_big)
      "Response too big to fit in message"

      iex> SnmpKit.SnmpMgr.Errors.description(:unknown_error)
      "Unknown error"
  """
  def description(error_atom) when is_atom(error_atom) do
    Map.get(@error_descriptions, error_atom, "Unknown error")
  end

  @doc """
  Translates an error code directly to a description.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.code_to_description(2)
      "Variable name not found"

      iex> SnmpKit.SnmpMgr.Errors.code_to_description(18)
      "Inconsistent name"
  """
  def code_to_description(error_code) when is_integer(error_code) do
    error_code
    |> code_to_atom()
    |> description()
  end

  @doc """
  Determines if an error is version-specific.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.v2c_error?(:no_access)
      true

      iex> SnmpKit.SnmpMgr.Errors.v2c_error?(:no_such_name)
      false
  """
  def v2c_error?(error_atom) do
    v2c_errors = [
      :no_access,
      :wrong_type,
      :wrong_length,
      :wrong_encoding,
      :wrong_value,
      :no_creation,
      :inconsistent_value,
      :resource_unavailable,
      :commit_failed,
      :undo_failed,
      :authorization_error,
      :not_writable,
      :inconsistent_name
    ]

    error_atom in v2c_errors
  end

  @doc """
  Formats an SNMP error for display.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.format_error({:snmp_error, 2})
      "SNMP Error (2): Variable name not found"

      iex> SnmpKit.SnmpMgr.Errors.format_error({:snmp_error, :no_such_name})
      "SNMP Error: Variable name not found"

      iex> SnmpKit.SnmpMgr.Errors.format_error({:v2c_error, :no_access, oid: "1.2.3.4"})
      "SNMPv2c Error: Access denied (OID: 1.2.3.4)"
  """
  def format_error({:snmp_error, error_code}) when is_integer(error_code) do
    error_atom = code_to_atom(error_code)
    desc = description(error_atom)
    "SNMP Error (#{error_code}): #{desc}"
  end

  def format_error({:snmp_error, error_atom}) when is_atom(error_atom) do
    desc = description(error_atom)
    "SNMP Error: #{desc}"
  end

  def format_error({:v2c_error, error_atom}) when is_atom(error_atom) do
    desc = description(error_atom)
    "SNMPv2c Error: #{desc}"
  end

  def format_error({:v2c_error, error_atom, details}) when is_atom(error_atom) do
    desc = description(error_atom)
    detail_str = format_error_details(details)
    "SNMPv2c Error: #{desc}#{detail_str}"
  end

  def format_error({:network_error, reason}) do
    "Network Error: #{inspect(reason)}"
  end

  def format_error({:timeout, _}) do
    "Timeout Error: Request timed out"
  end

  def format_error({:encoding_error, reason}) do
    "Encoding Error: #{inspect(reason)}"
  end

  def format_error({:decoding_error, reason}) do
    "Decoding Error: #{inspect(reason)}"
  end

  def format_error(error) do
    "Unknown Error: #{inspect(error)}"
  end

  @doc """
  Checks if an error is recoverable (can be retried).

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.recoverable?({:network_error, :host_unreachable})
      false

      iex> SnmpKit.SnmpMgr.Errors.recoverable?({:snmp_error, :too_big})
      true

      iex> SnmpKit.SnmpMgr.Errors.recoverable?(:timeout)
      true
  """
  def recoverable?({:network_error, :host_unreachable}), do: false
  def recoverable?({:network_error, :network_unreachable}), do: false
  def recoverable?({:snmp_error, :no_such_name}), do: false
  def recoverable?({:snmp_error, :bad_value}), do: false
  def recoverable?({:snmp_error, :read_only}), do: false
  def recoverable?({:v2c_error, :no_access}), do: false
  def recoverable?({:v2c_error, :not_writable}), do: false
  def recoverable?({:v2c_error, :wrong_type}), do: false
  def recoverable?(:timeout), do: true
  def recoverable?({:snmp_error, :too_big}), do: true
  def recoverable?({:snmp_error, :gen_err}), do: true
  def recoverable?(_), do: false

  @doc """
  Enhanced error analysis using SnmpKit.SnmpLib.Error for SNMP protocol errors.

  Provides detailed error information including severity and RFC compliance
  for SNMP protocol errors while preserving our comprehensive network error handling.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.analyze_error({:snmp_error, 2})
      %{
        type: :snmp_protocol,
        atom: :no_such_name,
        code: 2,
        severity: :error,
        retriable: false,
        category: :user_error,
        description: "Variable name not found"
      }

      iex> SnmpKit.SnmpMgr.Errors.analyze_error(:timeout)
      %{
        type: :network,
        atom: :timeout,
        retriable: true,
        category: :transient_error,
        description: "Operation timed out"
      }
  """
  def analyze_error(error) do
    case error do
      # SNMP protocol errors - use SnmpKit.SnmpLib.Error for detailed analysis
      {:snmp_error, code} when is_integer(code) ->
        if Error.valid_error_status?(code) do
          atom = Error.error_atom(code)

          %{
            type: :snmp_protocol,
            atom: atom,
            code: code,
            severity: Error.error_severity(atom),
            retriable: Error.retriable_error?(atom),
            rfc_compliant: true,
            category: classify_snmp_error(atom),
            description: description(atom)
          }
        else
          %{
            type: :snmp_protocol,
            atom: code_to_atom(code),
            code: code,
            retriable: false,
            rfc_compliant: false,
            category: :unknown_error,
            description: "Unknown SNMP error code"
          }
        end

      {:snmp_error, atom} when is_atom(atom) ->
        %{
          type: :snmp_protocol,
          atom: atom,
          code: Error.error_code(atom),
          severity: Error.error_severity(atom),
          retriable: Error.retriable_error?(atom),
          rfc_compliant: true,
          category: classify_snmp_error(atom),
          description: description(atom)
        }

      # Network errors - our superior handling
      error_atom
      when error_atom in [:timeout, :host_unreachable, :network_unreachable, :connection_refused] ->
        %{
          type: :network,
          atom: error_atom,
          retriable: recoverable?(error_atom),
          category: classify_network_error(error_atom),
          description: get_network_error_description(error_atom)
        }

      # Other errors
      _ ->
        {category, subcategory} = classify_error(error)

        %{
          type: :other,
          atom: error,
          retriable: recoverable?(error),
          category: category,
          subcategory: subcategory,
          description: format_error(error)
        }
    end
  end

  defp classify_snmp_error(atom) do
    case atom do
      atom when atom in [:no_such_name, :bad_value, :wrong_type, :wrong_value] ->
        :user_error

      atom when atom in [:read_only, :not_writable, :no_access, :authorization_error] ->
        :security_error

      atom when atom in [:too_big, :resource_unavailable] ->
        :resource_error

      atom when atom in [:gen_err, :commit_failed, :undo_failed] ->
        :device_error

      _ ->
        :protocol_error
    end
  end

  defp classify_network_error(atom) do
    case atom do
      :timeout -> :transient_error
      :host_unreachable -> :configuration_error
      :network_unreachable -> :configuration_error
      :connection_refused -> :service_error
    end
  end

  defp get_network_error_description(atom) do
    case atom do
      :timeout -> "Operation timed out"
      :host_unreachable -> "Host is unreachable"
      :network_unreachable -> "Network is unreachable"
      :connection_refused -> "Connection refused by target"
    end
  end

  # Private functions

  defp format_error_details(details) when is_list(details) do
    formatted =
      Enum.map_join(details, ", ", fn
        {:oid, oid} -> "OID: #{oid}"
        {:index, index} -> "Index: #{index}"
        {:value, value} -> "Value: #{inspect(value)}"
        {key, value} -> "#{key}: #{value}"
      end)

    if formatted == "", do: "", else: " (#{formatted})"
  end

  defp format_error_details(_), do: ""

  @doc """
  Classifies an error into a category for better handling.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.classify_error({:snmp_error, :no_such_name}, "Get request")
      {:user_error, :invalid_oid}

      iex> SnmpKit.SnmpMgr.Errors.classify_error({:network_error, :timeout}, "Network operation")
      {:transient_error, :network_timeout}

      iex> SnmpKit.SnmpMgr.Errors.classify_error({:snmp_error, :too_big}, "Bulk request")
      {:recoverable_error, :response_too_large}
  """
  def classify_error(error, context \\ nil)

  def classify_error({:snmp_error, :no_such_name}, _context) do
    {:user_error, :invalid_oid}
  end

  def classify_error({:snmp_error, :bad_value}, _context) do
    {:user_error, :invalid_value}
  end

  def classify_error({:snmp_error, :read_only}, _context) do
    {:user_error, :write_to_readonly}
  end

  def classify_error({:snmp_error, :too_big}, _context) do
    {:recoverable_error, :response_too_large}
  end

  def classify_error({:snmp_error, :gen_err}, _context) do
    {:device_error, :general_failure}
  end

  def classify_error({:v2c_error, :no_access}, _context) do
    {:security_error, :access_denied}
  end

  def classify_error({:v2c_error, :authorization_error}, _context) do
    {:security_error, :authorization_failed}
  end

  def classify_error({:v2c_error, :wrong_type}, _context) do
    {:user_error, :type_mismatch}
  end

  def classify_error({:v2c_error, :resource_unavailable}, _context) do
    {:device_error, :resource_exhausted}
  end

  def classify_error({:network_error, :timeout}, _context) do
    {:transient_error, :network_timeout}
  end

  def classify_error({:network_error, :host_unreachable}, _context) do
    {:configuration_error, :unreachable_host}
  end

  def classify_error({:network_error, :network_unreachable}, _context) do
    {:configuration_error, :network_unavailable}
  end

  def classify_error({:encoding_error, _reason}, _context) do
    {:protocol_error, :message_encoding_failed}
  end

  def classify_error({:decoding_error, _reason}, _context) do
    {:protocol_error, :message_decoding_failed}
  end

  def classify_error(:timeout, _context) do
    {:transient_error, :operation_timeout}
  end

  def classify_error(:invalid_oid_values, _context) do
    :validation_error
  end

  def classify_error({:snmp_encoding_error, _reason}, _context) do
    :validation_error
  end

  def classify_error(:encoding_failed, _context) do
    :validation_error
  end

  def classify_error(:authentication_error, _context) do
    :authentication_error
  end

  def classify_error(:invalid_community, _context) do
    :invalid_community
  end

  def classify_error(:bad_community, _context) do
    :invalid_community
  end

  def classify_error(:snmp_modules_not_available, _context) do
    :system_error
  end

  def classify_error(error, _context) do
    {:unknown_error, error}
  end

  @doc """
  Formats an error message in a user-friendly way with context.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.format_user_friendly_error({:snmp_error, :no_such_name}, "Getting system description")
      "Unable to get system description: The requested OID does not exist on the device"

      iex> SnmpKit.SnmpMgr.Errors.format_user_friendly_error({:network_error, :timeout}, "Contacting device")
      "Failed contacting device: The device did not respond within the timeout period"
  """
  def format_user_friendly_error(error, context \\ "Operation")

  def format_user_friendly_error({:snmp_error, :no_such_name}, context) do
    "#{context} failed: The requested OID does not exist on the device"
  end

  def format_user_friendly_error({:snmp_error, :bad_value}, context) do
    "#{context} failed: The provided value is invalid for this OID"
  end

  def format_user_friendly_error({:snmp_error, :read_only}, context) do
    "#{context} failed: This OID is read-only and cannot be modified"
  end

  def format_user_friendly_error({:snmp_error, :too_big}, context) do
    "#{context} failed: The response is too large. Try requesting fewer OIDs or use BULK operations"
  end

  def format_user_friendly_error({:snmp_error, :gen_err}, context) do
    "#{context} failed: The device reported a general error"
  end

  def format_user_friendly_error({:v2c_error, :no_access}, context) do
    "#{context} failed: Access denied. Check your community string and device configuration"
  end

  def format_user_friendly_error({:v2c_error, :authorization_error}, context) do
    "#{context} failed: Authorization failed. Verify your credentials"
  end

  def format_user_friendly_error({:v2c_error, :wrong_type}, context) do
    "#{context} failed: The value type does not match the expected type for this OID"
  end

  def format_user_friendly_error({:network_error, :timeout}, context) do
    "#{context} failed: The device did not respond within the timeout period"
  end

  def format_user_friendly_error({:network_error, :host_unreachable}, context) do
    "#{context} failed: Cannot reach the device. Check the IP address and network connectivity"
  end

  def format_user_friendly_error({:network_error, :network_unreachable}, context) do
    "#{context} failed: Network is unreachable. Check your network configuration"
  end

  def format_user_friendly_error(:timeout, context) do
    "#{context} timed out: The operation took too long to complete"
  end

  def format_user_friendly_error({:encoding_error, _reason}, context) do
    "#{context} failed: Unable to encode the SNMP message"
  end

  def format_user_friendly_error({:decoding_error, _reason}, context) do
    "#{context} failed: Unable to decode the SNMP response"
  end

  def format_user_friendly_error(error, context) do
    "#{context} failed: #{inspect(error)}"
  end

  @doc """
  Provides recovery suggestions for common errors.

  ## Examples

      iex> SnmpKit.SnmpMgr.Errors.get_recovery_suggestions({:snmp_error, :no_such_name})
      ["Verify the OID is correct", "Check if the OID is supported by this device", "Try using MIB browser to explore available OIDs"]

      iex> SnmpKit.SnmpMgr.Errors.get_recovery_suggestions({:network_error, :timeout})
      ["Increase timeout value", "Check network connectivity", "Verify device is responding to ping"]
  """
  def get_recovery_suggestions({:snmp_error, :no_such_name}) do
    [
      "Verify the OID is correct",
      "Check if the OID is supported by this device",
      "Try using MIB browser to explore available OIDs",
      "Ensure you're using the correct SNMP version"
    ]
  end

  def get_recovery_suggestions({:snmp_error, :bad_value}) do
    [
      "Check the value format and type",
      "Verify the value is within acceptable range",
      "Ensure the value matches the OID's expected data type",
      "Check device documentation for valid values"
    ]
  end

  def get_recovery_suggestions({:snmp_error, :read_only}) do
    [
      "This OID cannot be modified",
      "Use GET operation instead of SET",
      "Check device documentation for writable OIDs",
      "Verify you have the correct OID for writing"
    ]
  end

  def get_recovery_suggestions({:snmp_error, :too_big}) do
    [
      "Reduce the number of OIDs in the request",
      "Use GETBULK operation with smaller max-repetitions",
      "Split the request into multiple smaller requests",
      "Check device's maximum PDU size settings"
    ]
  end

  def get_recovery_suggestions({:v2c_error, :no_access}) do
    [
      "Verify the community string is correct",
      "Check device SNMP access configuration",
      "Ensure the device allows SNMP access from your IP",
      "Verify SNMP version compatibility (v1/v2c)"
    ]
  end

  def get_recovery_suggestions({:network_error, :timeout}) do
    [
      "Increase timeout value",
      "Check network connectivity to the device",
      "Verify device is responding to ping",
      "Check for network congestion or packet loss",
      "Ensure device SNMP service is running"
    ]
  end

  def get_recovery_suggestions({:network_error, :host_unreachable}) do
    [
      "Verify the IP address is correct",
      "Check network routing",
      "Ensure device is powered on and connected",
      "Test basic connectivity with ping",
      "Check firewall rules"
    ]
  end

  def get_recovery_suggestions({:encoding_error, _reason}) do
    [
      "Check OID format",
      "Verify value types are supported",
      "Ensure SNMP version compatibility",
      "Check for invalid characters in community string"
    ]
  end

  def get_recovery_suggestions({:decoding_error, _reason}) do
    [
      "Device may not support SNMP",
      "Check SNMP version compatibility",
      "Verify device is not sending corrupted responses",
      "Try different SNMP version (v1/v2c)"
    ]
  end

  def get_recovery_suggestions(:timeout) do
    [
      "Increase operation timeout",
      "Check if operation is too complex",
      "Verify device resources are available",
      "Try simpler operations first"
    ]
  end

  def get_recovery_suggestions(_error) do
    [
      "Check device documentation",
      "Verify SNMP configuration",
      "Try basic connectivity tests",
      "Contact device vendor support"
    ]
  end
end
