defmodule Microwaveprop.QrzTest do
  use Microwaveprop.DataCase, async: false

  alias Microwaveprop.Qrz
  alias Microwaveprop.Qrz.Callsign
  alias Microwaveprop.Qrz.Client
  alias Microwaveprop.Qrz.Record

  setup do
    Client.reset_session()
    :ok
  end

  defp stub_login_and_lookup(callsign_xml) do
    Req.Test.expect(Client, 2, fn conn ->
      params = Plug.Conn.fetch_query_params(conn).query_params

      if Map.has_key?(params, "username") do
        Plug.Conn.send_resp(conn, 200, """
        <?xml version="1.0" encoding="utf-8"?>
        <QRZDatabase version="1.34">
          <Session><Key>session123</Key></Session>
        </QRZDatabase>
        """)
      else
        Plug.Conn.send_resp(conn, 200, """
        <?xml version="1.0" encoding="utf-8"?>
        <QRZDatabase version="1.34">
          #{callsign_xml}
          <Session><Key>session123</Key></Session>
        </QRZDatabase>
        """)
      end
    end)
  end

  defp w1aw_xml do
    """
    <Callsign>
      <call>W1AW</call>
      <fname>Hiram Percy</fname>
      <name>Maxim</name>
      <grid>FN31pr</grid>
    </Callsign>
    """
  end

  describe "lookup_callsign/1" do
    test "fetches from API when not cached, stores in DB" do
      stub_login_and_lookup(w1aw_xml())

      assert {:ok, %Record{} = record} = Qrz.lookup_callsign("W1AW")
      assert record.call == "W1AW"
      assert record.fname == "Hiram Percy"
      assert record.grid == "FN31pr"

      assert Repo.get_by(Callsign, callsign: "W1AW")
    end

    test "returns cached data when within TTL" do
      {:ok, _} =
        %Callsign{}
        |> Callsign.changeset(%{callsign: "W1AW", data: %{"call" => "W1AW", "fname" => "Cached"}})
        |> Repo.insert()

      # Test config forces cache_ttl_hours: 0 so entries are always stale.
      # Override for this test to make the cache fresh.
      original = Application.get_env(:microwaveprop, Qrz)
      Application.put_env(:microwaveprop, Qrz, Keyword.put(original, :cache_ttl_hours, 24))

      on_exit(fn -> Application.put_env(:microwaveprop, Qrz, original) end)

      assert {:ok, %Record{} = record} = Qrz.lookup_callsign("W1AW")
      assert record.fname == "Cached"
    end

    test "re-fetches when cache entry is stale" do
      past =
        DateTime.utc_now()
        |> DateTime.add(-48, :hour)
        |> DateTime.truncate(:second)

      Repo.insert!(%Callsign{
        callsign: "W1AW",
        data: %{"call" => "W1AW", "fname" => "Old"},
        inserted_at: past,
        updated_at: past
      })

      stub_login_and_lookup(w1aw_xml())

      assert {:ok, %Record{} = record} = Qrz.lookup_callsign("W1AW")
      assert record.fname == "Hiram Percy"
    end

    test "upserts (updates existing record on re-fetch)" do
      past =
        DateTime.utc_now()
        |> DateTime.add(-48, :hour)
        |> DateTime.truncate(:second)

      original =
        Repo.insert!(%Callsign{
          callsign: "W1AW",
          data: %{"call" => "W1AW", "fname" => "Old"},
          inserted_at: past,
          updated_at: past
        })

      stub_login_and_lookup(w1aw_xml())

      assert {:ok, %Record{} = record} = Qrz.lookup_callsign("W1AW")
      assert record.fname == "Hiram Percy"

      assert Repo.get!(Callsign, original.id).data["fname"] == "Hiram Percy"
    end

    test "returns {:error, message} when API fails" do
      Req.Test.expect(Client, 2, fn conn ->
        params = Plug.Conn.fetch_query_params(conn).query_params

        if Map.has_key?(params, "username") do
          Plug.Conn.send_resp(conn, 200, """
          <?xml version="1.0" encoding="utf-8"?>
          <QRZDatabase version="1.34">
            <Session><Key>session123</Key></Session>
          </QRZDatabase>
          """)
        else
          Plug.Conn.send_resp(conn, 200, """
          <?xml version="1.0" encoding="utf-8"?>
          <QRZDatabase version="1.34">
            <Session>
              <Error>Not found: INVALIDCALL</Error>
              <Key>session123</Key>
            </Session>
          </QRZDatabase>
          """)
        end
      end)

      assert {:error, "Not found: INVALIDCALL"} = Qrz.lookup_callsign("INVALIDCALL")
    end

    test "normalizes callsign to uppercase" do
      stub_login_and_lookup(w1aw_xml())

      assert {:ok, %Record{call: "W1AW"}} = Qrz.lookup_callsign("w1aw")
    end
  end

  describe "get_callsign/1" do
    test "returns nil for unknown callsign" do
      assert Qrz.get_callsign("NONEXISTENT") == nil
    end

    test "returns record regardless of TTL" do
      past =
        DateTime.utc_now()
        |> DateTime.add(-999, :hour)
        |> DateTime.truncate(:second)

      Repo.insert!(%Callsign{
        callsign: "W1AW",
        data: %{"call" => "W1AW"},
        inserted_at: past,
        updated_at: past
      })

      assert %Callsign{callsign: "W1AW"} = Qrz.get_callsign("W1AW")
    end
  end
end
