defmodule Aprsme.DeviceCacheTest do
  use ExUnit.Case, async: false

  alias Aprsme.DeviceCache

  setup do
    pid = Process.whereis(DeviceCache)

    if pid do
      :erlang.unregister(DeviceCache)
    end

    on_exit(fn ->
      if pid && !Process.whereis(DeviceCache) do
        true = Process.register(pid, DeviceCache)
      end
    end)

    :ok
  end

  test "refresh_cache/0 returns :ok when the cache server is not running" do
    assert DeviceCache.refresh_cache() == :ok
  end

  test "lookup_device/1 returns nil on cache miss without crashing when server is not running" do
    assert DeviceCache.lookup_device("TEST-DEVICE") == nil
  end

  test "lookup_device/1 returns nil for nil input" do
    assert DeviceCache.lookup_device(nil) == nil
  end

  describe "wildcard pattern matching via cache" do
    setup do
      # Seed the cache directly with a small device list so we can exercise
      # the pattern-matching branches without running the GenServer.
      devices = [
        %Aprsme.Devices{identifier: "APSK21", vendor: "Kenwood", model: "TH-D74"},
        %Aprsme.Devices{identifier: "APS???", vendor: "Kenwood", model: "Unknown"},
        %Aprsme.Devices{identifier: "EXACT", vendor: "Test", model: "Exact"}
      ]

      Aprsme.Cache.put(:device_cache, :all_devices, devices)

      on_exit(fn -> Aprsme.Cache.put(:device_cache, :all_devices, []) end)
      :ok
    end

    test "exact identifier matches literally" do
      assert %{vendor: "Test"} = DeviceCache.lookup_device("EXACT")
    end

    test "returns nil for non-matching identifier" do
      assert DeviceCache.lookup_device("DOES-NOT-EXIST") == nil
    end

    test "prefers exact match when both wildcard and exact apply" do
      # Both EXACT (literal) and APS??? would not both apply here; just verify
      # that exact-string hits don't get confused with wildcard entries.
      assert %{identifier: "EXACT"} = DeviceCache.lookup_device("EXACT")
    end

    test "? wildcards match a single character" do
      # APSK21 matches APS??? (three wildcards after APS)
      assert %{identifier: _} = DeviceCache.lookup_device("APSK21")
    end

    test "wildcard pattern does not match if length mismatches" do
      # APS??? requires exactly 3 trailing chars; "APSXX" has only 2 and
      # shouldn't match the wildcard entry (nor is there any other entry).
      assert DeviceCache.lookup_device("APSXX") == nil
    end
  end

  describe "GenServer callbacks" do
    test "init/1 schedules an initial load and returns an empty state" do
      assert {:ok, state} = DeviceCache.init([])
      assert state == %{initial_load_done: false}
      assert_receive :initial_load, 1000
    end

    test "handle_call(:refresh_cache, _, state) returns :ok" do
      state = %{initial_load_done: true}
      assert {:reply, :ok, ^state} = DeviceCache.handle_call(:refresh_cache, self(), state)
    end

    test "handle_cast(:refresh_cache_async, state) keeps the state" do
      state = %{initial_load_done: true}
      assert {:noreply, ^state} = DeviceCache.handle_cast(:refresh_cache_async, state)
    end

    test "handle_info(:initial_load, state) flips initial_load_done and schedules a periodic refresh" do
      assert {:noreply, %{initial_load_done: true}} =
               DeviceCache.handle_info(:initial_load, %{initial_load_done: false})
    end

    test "handle_info(:refresh_cache, state) keeps the state and schedules a periodic refresh" do
      state = %{initial_load_done: true}
      assert {:noreply, ^state} = DeviceCache.handle_info(:refresh_cache, state)
    end

    test "refresh_cache/0 calls into the running GenServer process" do
      # Start a temporary DeviceCache to exercise the live-process branch
      # (line 51: _pid -> GenServer.call(...)).
      {:ok, pid} = GenServer.start_link(DeviceCache, [])

      original_pid = Process.whereis(DeviceCache)
      if original_pid, do: :erlang.unregister(DeviceCache)
      Process.register(pid, DeviceCache)

      try do
        assert DeviceCache.refresh_cache() == :ok
      after
        if Process.whereis(DeviceCache) == pid, do: :erlang.unregister(DeviceCache)

        if original_pid && !Process.whereis(DeviceCache) do
          Process.register(original_pid, DeviceCache)
        end

        Process.exit(pid, :normal)
      end
    end

    test "initial_load in non-test env loads real devices from the DB" do
      # The load_devices_into_cache non-test branch reads Repo.all(Devices).
      # Swap env, then trigger and restore.
      original_env = Application.get_env(:aprsme, :env)
      Application.put_env(:aprsme, :env, :dev)

      on_exit(fn -> Application.put_env(:aprsme, :env, original_env) end)

      try do
        result = DeviceCache.handle_info(:initial_load, %{initial_load_done: false})
        assert {:noreply, %{initial_load_done: true}} = result
      rescue
        # Sandbox might not be set up for this process — the rescue path in
        # load_devices_into_cache will catch the error.
        _ -> :ok
      end
    end
  end
end
