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

  alias SnmpKit.SnmpLib.MIB
  alias SnmpKit.SnmpLib.MIB.Error

  describe "compile/2" do
    test "returns error for non-existent file" do
      result = MIB.compile("nonexistent.mib")

      assert {:error, errors} = result
      assert is_list(errors)
      refute Enum.empty?(errors)
      assert %Error{type: :file_not_found} = List.first(errors)
    end

    test "returns error for invalid file path" do
      result = MIB.compile("/invalid/path/to/mib.txt")

      assert {:error, errors} = result
      assert is_list(errors)
    end

    test "accepts empty options list" do
      result = MIB.compile("test.mib", [])

      assert {:error, _errors} = result
    end

    test "accepts output_dir option" do
      result = MIB.compile("test.mib", output_dir: "/tmp/mibs")

      assert {:error, _errors} = result
    end

    test "accepts include_dirs option" do
      result = MIB.compile("test.mib", include_dirs: ["./priv/mibs", "/usr/share/mibs"])

      assert {:error, _errors} = result
    end

    test "accepts log_level option" do
      result = MIB.compile("test.mib", log_level: :debug)

      assert {:error, _errors} = result
    end

    test "accepts format option with :elixir" do
      result = MIB.compile("test.mib", format: :elixir)

      assert {:error, _errors} = result
    end

    test "accepts format option with :erlang" do
      result = MIB.compile("test.mib", format: :erlang)

      assert {:error, _errors} = result
    end

    test "accepts format option with :both" do
      result = MIB.compile("test.mib", format: :both)

      assert {:error, _errors} = result
    end

    test "accepts optimize option" do
      result = MIB.compile("test.mib", optimize: true)

      assert {:error, _errors} = result
    end

    test "accepts warnings_as_errors option" do
      result = MIB.compile("test.mib", warnings_as_errors: true)

      assert {:error, _errors} = result
    end

    test "accepts vendor_quirks option" do
      result = MIB.compile("test.mib", vendor_quirks: false)

      assert {:error, _errors} = result
    end

    test "accepts multiple options together" do
      result =
        MIB.compile("test.mib",
          output_dir: "/tmp",
          format: :elixir,
          optimize: true,
          log_level: :warn
        )

      assert {:error, _errors} = result
    end

    test "returns error list with Error structs" do
      result = MIB.compile("nonexistent.mib")

      assert {:error, errors} = result
      assert Enum.all?(errors, &match?(%Error{}, &1))
    end

    test "accepts relative file path" do
      result = MIB.compile("./test/fixtures/test.mib")

      assert {:error, _errors} = result
    end

    test "accepts absolute file path" do
      result = MIB.compile("/absolute/path/to/mib.txt")

      assert {:error, _errors} = result
    end
  end

  describe "compile_string/2" do
    @tag :yecc_required
    test "returns error for empty string" do
      result = MIB.compile_string("")

      assert {:error, errors} = result
      assert is_list(errors)
    end

    @tag :yecc_required
    test "returns error for invalid MIB content" do
      result = MIB.compile_string("invalid mib content")

      assert {:error, errors} = result
      assert is_list(errors)
    end

    @tag :yecc_required
    test "accepts MIB header format" do
      mib_content = """
      TEST-MIB DEFINITIONS ::= BEGIN
      END
      """

      result = MIB.compile_string(mib_content)

      # Should attempt to compile (may fail, but accepts the format)
      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    @tag :yecc_required
    test "accepts empty options list" do
      result = MIB.compile_string("TEST-MIB DEFINITIONS ::= BEGIN END", [])

      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    @tag :yecc_required
    test "accepts output_dir option" do
      result = MIB.compile_string("content", output_dir: "/tmp")

      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "accepts format option" do
      result = MIB.compile_string("content", format: :elixir)

      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "accepts include_dirs option" do
      result = MIB.compile_string("content", include_dirs: ["./priv/mibs"])

      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "accepts multiple options" do
      result =
        MIB.compile_string("content",
          output_dir: "/tmp",
          format: :erlang,
          optimize: false
        )

      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "handles binary string input" do
      result = MIB.compile_string(<<65, 66, 67>>)

      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "handles long MIB content" do
      long_content = String.duplicate("TEST-MIB DEFINITIONS ::= BEGIN\n", 100) <> "END"

      result = MIB.compile_string(long_content)

      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    @tag :yecc_required
    test "returns error list for invalid content" do
      result = MIB.compile_string("invalid")

      assert {:error, errors} = result
      assert Enum.all?(errors, &match?(%Error{}, &1))
    end
  end

  describe "load_compiled/1" do
    test "returns error for non-existent compiled file" do
      result = MIB.load_compiled("nonexistent_compiled.ex")

      assert {:error, _reason} = result
    end

    test "returns error for invalid file path" do
      result = MIB.load_compiled("/invalid/path/compiled.ex")

      assert {:error, _reason} = result
    end

    test "accepts .ex file extension" do
      result = MIB.load_compiled("lib/generated/test_mib.ex")

      assert {:error, _reason} = result
    end

    test "accepts .beam file extension" do
      result = MIB.load_compiled("lib/generated/test_mib.beam")

      assert {:error, _reason} = result
    end

    test "accepts relative path" do
      result = MIB.load_compiled("./compiled/mib.ex")

      assert {:error, _reason} = result
    end

    test "accepts absolute path" do
      result = MIB.load_compiled("/absolute/path/to/compiled.ex")

      assert {:error, _reason} = result
    end

    test "error includes reason" do
      result = MIB.load_compiled("nonexistent.ex")

      assert {:error, reason} = result
      assert is_atom(reason) or is_binary(reason) or is_tuple(reason)
    end
  end

  describe "compile_all/2" do
    test "returns error for empty list" do
      result = MIB.compile_all([])

      # Empty list might return {:ok, []} or {:error, ...} depending on implementation
      assert is_tuple(result) and elem(result, 0) in [:ok, :error]
    end

    test "returns error for list with non-existent files" do
      result = MIB.compile_all(["nonexistent1.mib", "nonexistent2.mib"])

      assert {:error, errors} = result
      assert is_list(errors)
    end

    test "accepts single file in list" do
      result = MIB.compile_all(["test.mib"])

      assert {:error, _errors} = result
    end

    test "accepts multiple files in list" do
      result = MIB.compile_all(["file1.mib", "file2.mib", "file3.mib"])

      assert {:error, _errors} = result
    end

    test "accepts empty options list" do
      result = MIB.compile_all(["test.mib"], [])

      assert {:error, _errors} = result
    end

    test "accepts output_dir option" do
      result = MIB.compile_all(["test.mib"], output_dir: "/tmp")

      assert {:error, _errors} = result
    end

    test "accepts format option" do
      result = MIB.compile_all(["test.mib"], format: :elixir)

      assert {:error, _errors} = result
    end

    test "accepts include_dirs option" do
      result = MIB.compile_all(["test.mib"], include_dirs: ["./mibs"])

      assert {:error, _errors} = result
    end

    test "accepts multiple options" do
      result =
        MIB.compile_all(["test.mib"],
          output_dir: "/tmp",
          format: :both,
          optimize: true
        )

      assert {:error, _errors} = result
    end

    test "handles mix of valid and invalid file paths" do
      result = MIB.compile_all(["valid.mib", "/invalid/path.mib", "another.mib"])

      assert {:error, _errors} = result
    end

    test "error includes file associations" do
      result = MIB.compile_all(["file1.mib", "file2.mib"])

      assert {:error, errors} = result
      # Errors should be associated with files
      assert is_list(errors)
    end

    test "accepts list with dependency order" do
      # Simulating dependency order: base MIBs first, dependent MIBs later
      result = MIB.compile_all(["SNMPv2-SMI.txt", "SNMPv2-TC.txt", "RFC1213-MIB.txt"])

      assert {:error, _errors} = result
    end
  end

  describe "option validation" do
    test "compile handles invalid option keys gracefully" do
      result = MIB.compile("test.mib", invalid_key: "value")

      # Should still attempt to compile, ignoring invalid keys
      assert {:error, _errors} = result
    end

    @tag :yecc_required
    test "compile_string handles invalid option keys gracefully" do
      result = MIB.compile_string("content", invalid_key: "value")

      assert {:error, _errors} = result
    end

    test "compile_all handles invalid option keys gracefully" do
      result = MIB.compile_all(["test.mib"], invalid_key: "value")

      assert {:error, _errors} = result
    end
  end

  describe "return value structure" do
    test "compile returns tuple with :error or :ok" do
      result = MIB.compile("test.mib")

      assert is_tuple(result)
      assert tuple_size(result) == 2
      assert elem(result, 0) in [:ok, :error, :warning]
    end

    @tag :yecc_required
    test "compile_string returns tuple with :error or :ok" do
      result = MIB.compile_string("content")

      assert is_tuple(result)
      assert tuple_size(result) == 2
      assert elem(result, 0) in [:ok, :error, :warning]
    end

    test "load_compiled returns tuple with :error or :ok" do
      result = MIB.load_compiled("compiled.ex")

      assert is_tuple(result)
      assert tuple_size(result) == 2
      assert elem(result, 0) in [:ok, :error]
    end

    test "compile_all returns tuple with :error or :ok" do
      result = MIB.compile_all(["test.mib"])

      assert is_tuple(result)
      assert tuple_size(result) == 2
      assert elem(result, 0) in [:ok, :error]
    end
  end

  describe "error handling" do
    test "compile handles various file path errors gracefully" do
      # These should all return errors without crashing
      assert {:error, _} = MIB.compile("")
      assert {:error, _} = MIB.compile("/nonexistent/path")
      assert {:error, _} = MIB.compile("../../../etc/passwd")
    end

    @tag :yecc_required
    test "compile_string handles invalid content gracefully" do
      assert {:error, _} = MIB.compile_string("")
      assert {:error, _} = MIB.compile_string("not valid MIB content")
      assert {:error, _} = MIB.compile_string("123 456 789")
    end

    test "load_compiled handles invalid paths gracefully" do
      assert {:error, _} = MIB.load_compiled("")
      assert {:error, _} = MIB.load_compiled("/nonexistent/file.ex")
      assert {:error, _} = MIB.load_compiled("../invalid/path.ex")
    end

    test "compile_all handles empty and invalid lists gracefully" do
      # Empty list case
      result = MIB.compile_all([])
      assert is_tuple(result) and elem(result, 0) in [:ok, :error]

      # List with invalid paths
      assert {:error, _} = MIB.compile_all(["", "/nonexistent"])
    end
  end

  describe "default options" do
    test "compile uses defaults when options not provided" do
      result1 = MIB.compile("test.mib")
      result2 = MIB.compile("test.mib", [])

      # Both should behave similarly with defaults
      assert match?({:error, _}, result1)
      assert match?({:error, _}, result2)
    end

    @tag :yecc_required
    test "compile_string uses defaults when options not provided" do
      result1 = MIB.compile_string("content")
      result2 = MIB.compile_string("content", [])

      assert match?({:error, _}, result1)
      assert match?({:error, _}, result2)
    end

    test "compile_all uses defaults when options not provided" do
      result1 = MIB.compile_all(["test.mib"])
      result2 = MIB.compile_all(["test.mib"], [])

      assert match?({:error, _}, result1)
      assert match?({:error, _}, result2)
    end
  end
end
