defmodule SnmpKit.SnmpLib.MIB.ASTTest do
  use ExUnit.Case, async: true

  alias SnmpKit.SnmpLib.MIB.AST

  describe "new_mib/2" do
    test "creates basic MIB node" do
      mib = AST.new_mib("TEST-MIB")

      assert mib.__type__ == :mib
      assert mib.name == "TEST-MIB"
      assert mib.imports == []
      assert mib.definitions == []
      assert mib.oid_tree == %{}
    end

    test "creates MIB node with options" do
      mib =
        AST.new_mib("TEST-MIB",
          last_updated: "202501010000Z",
          organization: "Test Org",
          contact_info: "test@example.com",
          description: "Test MIB",
          revision_history: [
            %{
              __type__: :revision,
              date: "202501010000Z",
              description: "Initial version",
              line: 1
            }
          ]
        )

      assert mib.last_updated == "202501010000Z"
      assert mib.organization == "Test Org"
      assert mib.contact_info == "test@example.com"
      assert mib.description == "Test MIB"
      assert length(mib.revision_history) == 1
    end
  end

  describe "new_object_type/2" do
    test "creates object type definition" do
      obj =
        AST.new_object_type("sysDescr",
          syntax: :octet_string,
          max_access: :read_only,
          status: :current,
          description: "System description",
          oid: [1, 3, 6, 1, 2, 1, 1, 1],
          line: 10
        )

      assert obj.__type__ == :object_type
      assert obj.name == "sysDescr"
      assert obj.syntax == :octet_string
      assert obj.max_access == :read_only
      assert obj.status == :current
      assert obj.description == "System description"
      assert obj.oid == [1, 3, 6, 1, 2, 1, 1, 1]
      assert obj.line == 10
    end

    test "creates object type with optional fields" do
      obj =
        AST.new_object_type("ifDescr",
          syntax: {:octet_string, [{:size, 0..255}]},
          units: "bytes",
          max_access: :read_only,
          status: :current,
          description: "Interface description",
          reference: "RFC 2863",
          index: {:index, ["ifIndex"]},
          oid: [1, 3, 6, 1, 2, 1, 2, 2, 1, 2],
          line: 20
        )

      assert obj.units == "bytes"
      assert obj.reference == "RFC 2863"
      assert obj.index == {:index, ["ifIndex"]}
    end
  end

  describe "new_object_identity/2" do
    test "creates object identity definition" do
      obj =
        AST.new_object_identity("system",
          status: :current,
          description: "System group",
          oid: [1, 3, 6, 1, 2, 1, 1],
          line: 5
        )

      assert obj.__type__ == :object_identity
      assert obj.name == "system"
      assert obj.status == :current
      assert obj.description == "System group"
      assert obj.oid == [1, 3, 6, 1, 2, 1, 1]
    end

    test "creates object identity with reference" do
      obj =
        AST.new_object_identity("interfaces",
          status: :current,
          description: "Interfaces group",
          reference: "RFC 2863",
          oid: [1, 3, 6, 1, 2, 1, 2],
          line: 15
        )

      assert obj.reference == "RFC 2863"
    end
  end

  describe "new_import/3" do
    test "creates import statement" do
      import_stmt = AST.new_import(["Counter32", "Gauge32"], "SNMPv2-SMI", 3)

      assert import_stmt.__type__ == :import
      assert import_stmt.symbols == ["Counter32", "Gauge32"]
      assert import_stmt.from_module == "SNMPv2-SMI"
      assert import_stmt.line == 3
    end

    test "creates empty import" do
      import_stmt = AST.new_import([], "SNMPv2-TC", 1)

      assert import_stmt.symbols == []
    end
  end

  describe "determine_snmp_version/1" do
    test "identifies SNMPv2 with MODULE-IDENTITY" do
      definitions = [
        %{
          __type__: :module_identity,
          name: "testMIB",
          last_updated: "202501010000Z",
          organization: "Test",
          contact_info: "test@test.com",
          description: "Test MIB",
          revision_history: [],
          oid: [1, 3, 6, 1, 4, 1, 999],
          line: 1
        }
      ]

      assert :v2c = AST.determine_snmp_version(definitions)
    end

    test "identifies SNMPv1 without MODULE-IDENTITY" do
      definitions = [
        %{
          __type__: :object_type,
          name: "sysDescr",
          syntax: :octet_string,
          max_access: :read_only,
          status: :mandatory,
          description: "Test",
          oid: [1, 3, 6, 1, 2, 1, 1, 1],
          line: 10
        }
      ]

      assert :v1 = AST.determine_snmp_version(definitions)
    end

    test "returns v1 for empty definitions" do
      assert :v1 = AST.determine_snmp_version([])
    end
  end

  describe "build_oid_tree/1" do
    test "builds OID tree from definitions" do
      definitions = [
        %{
          __type__: :object_type,
          name: "sysDescr",
          syntax: :octet_string,
          max_access: :read_only,
          status: :current,
          description: "Test",
          oid: [1, 3, 6, 1, 2, 1, 1, 1],
          line: 1
        },
        %{
          __type__: :object_identity,
          name: "system",
          status: :current,
          description: "System group",
          oid: [1, 3, 6, 1, 2, 1, 1],
          line: 2
        }
      ]

      tid = AST.build_oid_tree(definitions)

      # Verify ETS table was created
      assert :ets.info(tid) != :undefined

      # Verify OID mappings
      assert [{[1, 3, 6, 1, 2, 1, 1, 1], "sysDescr"}] =
               :ets.lookup(tid, [1, 3, 6, 1, 2, 1, 1, 1])

      assert [{"sysDescr", [1, 3, 6, 1, 2, 1, 1, 1]}] = :ets.lookup(tid, "sysDescr")

      # Clean up
      :ets.delete(tid)
    end

    test "handles definitions without OIDs" do
      definitions = [
        %{
          __type__: :textual_convention,
          name: "DisplayString",
          status: :current,
          description: "Display string",
          syntax: :octet_string,
          line: 1
        }
      ]

      tid = AST.build_oid_tree(definitions)

      # Should create table but with no entries
      assert :ets.info(tid, :size) == 0

      :ets.delete(tid)
    end
  end

  describe "validate_node/1" do
    test "validates mib node" do
      node = %{__type__: :mib, name: "TEST-MIB"}
      assert {:ok, ^node} = AST.validate_node(node)
    end

    test "validates object_type node" do
      node = %{__type__: :object_type, name: "sysDescr"}
      assert {:ok, ^node} = AST.validate_node(node)
    end

    test "validates all node types" do
      valid_types = [
        :mib,
        :object_type,
        :object_identity,
        :module_identity,
        :object_group,
        :notification_type,
        :notification_group,
        :module_compliance,
        :agent_capabilities,
        :textual_convention,
        :trap_type,
        :object_identifier_assignment,
        :import,
        :revision
      ]

      for type <- valid_types do
        node = %{__type__: type, name: "test"}
        assert {:ok, ^node} = AST.validate_node(node)
      end
    end

    test "rejects invalid node" do
      assert {:error, msg} = AST.validate_node(%{invalid: true})
      assert msg =~ "Invalid AST node"
    end

    test "rejects node with invalid type" do
      assert {:error, msg} = AST.validate_node(%{__type__: :invalid_type})
      assert msg =~ "Invalid AST node"
    end
  end

  describe "pretty_print/1" do
    test "prints node with type and name" do
      node = %{__type__: :object_type, name: "sysDescr"}
      assert "object_type: sysDescr" = AST.pretty_print(node)
    end

    test "prints node with type only" do
      node = %{__type__: :mib}
      assert "mib" = AST.pretty_print(node)
    end

    test "prints other values with inspect" do
      assert "123" = AST.pretty_print(123)
      assert ~s("test") = AST.pretty_print("test")
    end
  end
end
