defmodule Towerops.Snmp.Adapters.ReplayTest do
  use ExUnit.Case, async: true

  alias Towerops.Snmp.Adapters.Replay

  describe "new/1" do
    test "creates connection opts with OID map" do
      oid_map = %{
        "1.3.6.1.2.1.1.1.0" => "Cisco IOS Software",
        "1.3.6.1.2.1.2.2.1.1.1" => "1"
      }

      opts = Replay.new(oid_map)

      assert Keyword.get(opts, :adapter) == Replay
      assert Keyword.get(opts, :oid_map) == oid_map
      assert Keyword.get(opts, :ip) == "replay"
      assert Keyword.get(opts, :community) == "public"
      assert Keyword.get(opts, :version) == "2c"
      assert Keyword.get(opts, :port) == 161
    end

    test "works with empty OID map" do
      opts = Replay.new(%{})

      assert Keyword.get(opts, :adapter) == Replay
      assert Keyword.get(opts, :oid_map) == %{}
    end
  end

  describe "get/2" do
    test "returns value for existing OID" do
      oid_map = %{"1.3.6.1.2.1.1.1.0" => "Cisco IOS Software"}
      opts = Replay.new(oid_map)

      assert {:ok, "Cisco IOS Software"} = Replay.get(opts, "1.3.6.1.2.1.1.1.0")
    end

    test "returns error for non-existent OID" do
      opts = Replay.new(%{})

      assert {:error, :no_such_name} = Replay.get(opts, "1.3.6.1.2.1.1.1.0")
    end

    test "parses integer values" do
      oid_map = %{"1.3.6.1.2.1.2.2.1.1.1" => "42"}
      opts = Replay.new(oid_map)

      assert {:ok, 42} = Replay.get(opts, "1.3.6.1.2.1.2.2.1.1.1")
    end

    test "parses float values" do
      oid_map = %{"1.3.6.1.2.1.25.3.3.1.2.1" => "98.6"}
      opts = Replay.new(oid_map)

      assert {:ok, 98.6} = Replay.get(opts, "1.3.6.1.2.1.25.3.3.1.2.1")
    end

    test "parses OID list values" do
      oid_map = %{"1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.9.1.516"}
      opts = Replay.new(oid_map)

      assert {:ok, [1, 3, 6, 1, 4, 1, 9, 1, 516]} =
               Replay.get(opts, "1.3.6.1.2.1.1.2.0")
    end

    test "parses MAC address values" do
      oid_map = %{"1.3.6.1.2.1.2.2.1.6.1" => "aa:bb:cc:dd:ee:ff"}
      opts = Replay.new(oid_map)

      assert {:ok, "aa:bb:cc:dd:ee:ff"} = Replay.get(opts, "1.3.6.1.2.1.2.2.1.6.1")
    end

    test "parses IPv4 address values" do
      oid_map = %{"1.3.6.1.2.1.4.20.1.1.192.168.1.1" => "192.168.1.1"}
      opts = Replay.new(oid_map)

      assert {:ok, "192.168.1.1"} = Replay.get(opts, "1.3.6.1.2.1.4.20.1.1.192.168.1.1")
    end

    test "keeps string values as strings" do
      oid_map = %{"1.3.6.1.2.1.1.5.0" => "router-01.example.com"}
      opts = Replay.new(oid_map)

      assert {:ok, "router-01.example.com"} = Replay.get(opts, "1.3.6.1.2.1.1.5.0")
    end
  end

  describe "walk/2" do
    test "returns all child OIDs under base" do
      oid_map = %{
        "1.3.6.1.2.1.2.2.1.1.1" => "1",
        "1.3.6.1.2.1.2.2.1.1.2" => "2",
        "1.3.6.1.2.1.2.2.1.2.1" => "GigabitEthernet0/1",
        "1.3.6.1.2.1.2.2.1.2.2" => "GigabitEthernet0/2"
      }

      opts = Replay.new(oid_map)

      assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1")

      assert length(results) == 2
      assert {"1.3.6.1.2.1.2.2.1.1.1", 1} in results
      assert {"1.3.6.1.2.1.2.2.1.1.2", 2} in results
    end

    test "returns empty list when no children exist" do
      oid_map = %{"1.3.6.1.2.1.1.1.0" => "Cisco IOS Software"}
      opts = Replay.new(oid_map)

      assert {:ok, []} = Replay.walk(opts, "1.3.6.1.2.1.99")
    end

    test "does not include base OID itself" do
      oid_map = %{
        "1.3.6.1.2.1.2.2.1.1" => "base",
        "1.3.6.1.2.1.2.2.1.1.1" => "child"
      }

      opts = Replay.new(oid_map)

      assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1")

      assert length(results) == 1
      assert {"1.3.6.1.2.1.2.2.1.1.1", "child"} in results
      refute {"1.3.6.1.2.1.2.2.1.1", "base"} in results
    end

    test "returns results in lexicographic order" do
      oid_map = %{
        "1.3.6.1.2.1.2.2.1.1.10" => "10",
        "1.3.6.1.2.1.2.2.1.1.2" => "2",
        "1.3.6.1.2.1.2.2.1.1.1" => "1"
      }

      opts = Replay.new(oid_map)

      assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1")

      # Extract OIDs
      oids = Enum.map(results, fn {oid, _} -> oid end)

      # Should be sorted: 1, 2, 10 (not "1", "10", "2" as strings)
      assert oids == [
               "1.3.6.1.2.1.2.2.1.1.1",
               "1.3.6.1.2.1.2.2.1.1.2",
               "1.3.6.1.2.1.2.2.1.1.10"
             ]
    end

    test "parses values in walk results" do
      oid_map = %{
        "1.3.6.1.2.1.2.2.1.1.1" => "42",
        "1.3.6.1.2.1.2.2.1.1.2" => "98.6",
        "1.3.6.1.2.1.2.2.1.1.3" => "192.168.1.1"
      }

      opts = Replay.new(oid_map)

      assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1")

      assert {"1.3.6.1.2.1.2.2.1.1.1", 42} in results
      assert {"1.3.6.1.2.1.2.2.1.1.2", 98.6} in results
      assert {"1.3.6.1.2.1.2.2.1.1.3", "192.168.1.1"} in results
    end
  end

  describe "get_multiple/2" do
    test "returns all requested OIDs" do
      oid_map = %{
        "1.3.6.1.2.1.1.1.0" => "Cisco IOS Software",
        "1.3.6.1.2.1.1.5.0" => "router-01",
        "1.3.6.1.2.1.1.6.0" => "Data Center 1"
      }

      opts = Replay.new(oid_map)

      oids = [
        "1.3.6.1.2.1.1.1.0",
        "1.3.6.1.2.1.1.5.0",
        "1.3.6.1.2.1.1.6.0"
      ]

      assert {:ok, results} = Replay.get_multiple(opts, oids)

      assert results["1.3.6.1.2.1.1.1.0"] == "Cisco IOS Software"
      assert results["1.3.6.1.2.1.1.5.0"] == "router-01"
      assert results["1.3.6.1.2.1.1.6.0"] == "Data Center 1"
    end

    test "returns nil for non-existent OIDs" do
      oid_map = %{"1.3.6.1.2.1.1.1.0" => "Cisco IOS Software"}
      opts = Replay.new(oid_map)

      oids = [
        "1.3.6.1.2.1.1.1.0",
        "1.3.6.1.2.1.99.99.99"
      ]

      assert {:ok, results} = Replay.get_multiple(opts, oids)

      assert results["1.3.6.1.2.1.1.1.0"] == "Cisco IOS Software"
      assert results["1.3.6.1.2.1.99.99.99"] == nil
    end

    test "works with empty OID list" do
      opts = Replay.new(%{})

      assert {:ok, %{}} = Replay.get_multiple(opts, [])
    end

    test "parses values correctly" do
      oid_map = %{
        "1.3.6.1.2.1.2.2.1.1.1" => "42",
        "1.3.6.1.2.1.2.2.1.2.1" => "GigabitEthernet0/1"
      }

      opts = Replay.new(oid_map)

      oids = [
        "1.3.6.1.2.1.2.2.1.1.1",
        "1.3.6.1.2.1.2.2.1.2.1"
      ]

      assert {:ok, results} = Replay.get_multiple(opts, oids)

      assert results["1.3.6.1.2.1.2.2.1.1.1"] == 42
      assert results["1.3.6.1.2.1.2.2.1.2.1"] == "GigabitEthernet0/1"
    end
  end

  describe "leading dot normalization (gosnmp format)" do
    test "get/2 finds value when map has leading-dot keys" do
      # gosnmp returns OIDs like ".1.3.6.1.2.1.1.1.0" (with leading dot)
      # but server code looks up "1.3.6.1.2.1.1.1.0" (without leading dot)
      oid_map = %{".1.3.6.1.2.1.1.1.0" => "RouterOS CHR"}
      opts = Replay.new(oid_map)

      assert {:ok, "RouterOS CHR"} = Replay.get(opts, "1.3.6.1.2.1.1.1.0")
    end

    test "get/2 finds value when map has no-dot keys but lookup has dot" do
      oid_map = %{"1.3.6.1.2.1.1.1.0" => "Cisco IOS"}
      opts = Replay.new(oid_map)

      assert {:ok, "Cisco IOS"} = Replay.get(opts, ".1.3.6.1.2.1.1.1.0")
    end

    test "get_multiple/2 finds values with mixed dot formats" do
      oid_map = %{
        ".1.3.6.1.2.1.1.1.0" => "RouterOS CHR",
        ".1.3.6.1.2.1.1.5.0" => "mikrotik-router"
      }

      opts = Replay.new(oid_map)

      oids = ["1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.5.0"]

      assert {:ok, results} = Replay.get_multiple(opts, oids)
      assert results["1.3.6.1.2.1.1.1.0"] == "RouterOS CHR"
      assert results["1.3.6.1.2.1.1.5.0"] == "mikrotik-router"
    end

    test "walk/2 handles leading-dot keys in map" do
      oid_map = %{
        ".1.3.6.1.2.1.2.2.1.1.1" => "1",
        ".1.3.6.1.2.1.2.2.1.1.2" => "2"
      }

      opts = Replay.new(oid_map)

      assert {:ok, results} = Replay.walk(opts, "1.3.6.1.2.1.2.2.1.1")
      assert length(results) == 2
    end
  end

  describe "type inference edge cases" do
    test "parses MAC address with hyphens" do
      oid_map = %{"oid" => "AA-BB-CC-DD-EE-FF"}
      opts = Replay.new(oid_map)

      assert {:ok, "AA-BB-CC-DD-EE-FF"} = Replay.get(opts, "oid")
    end

    test "treats 4-component dotted number as IPv4, not OID" do
      # Ambiguous case: "1.2.3.4" could be IPv4 or OID, we choose IPv4
      oid_map = %{"oid" => "1.2.3.4"}
      opts = Replay.new(oid_map)

      assert {:ok, "1.2.3.4"} = Replay.get(opts, "oid")
    end

    test "parses 5-component dotted number as OID list" do
      oid_map = %{"oid" => "1.2.3.4.5"}
      opts = Replay.new(oid_map)

      assert {:ok, [1, 2, 3, 4, 5]} = Replay.get(opts, "oid")
    end

    test "parses negative float" do
      oid_map = %{"oid" => "-12.5"}
      opts = Replay.new(oid_map)

      assert {:ok, -12.5} = Replay.get(opts, "oid")
    end

    test "keeps mixed alphanumeric as string" do
      oid_map = %{"oid" => "GigabitEthernet0/1"}
      opts = Replay.new(oid_map)

      assert {:ok, "GigabitEthernet0/1"} = Replay.get(opts, "oid")
    end

    test "handles zero value" do
      oid_map = %{"oid" => "0"}
      opts = Replay.new(oid_map)

      assert {:ok, 0} = Replay.get(opts, "oid")
    end

    test "handles large integer" do
      oid_map = %{"oid" => "4294967295"}
      opts = Replay.new(oid_map)

      assert {:ok, 4_294_967_295} = Replay.get(opts, "oid")
    end

    test "parses negative integer" do
      oid_map = %{"oid" => "-3"}
      opts = Replay.new(oid_map)

      assert {:ok, -3} = Replay.get(opts, "oid")
    end

    test "parses negative large integer" do
      oid_map = %{"oid" => "-1000"}
      opts = Replay.new(oid_map)

      assert {:ok, -1000} = Replay.get(opts, "oid")
    end
  end
end
