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

  alias Microwaveprop.CallsignLocation
  alias Microwaveprop.CallsignLocation.Location
  alias Microwaveprop.Geocoder
  alias Microwaveprop.Qrz.Client

  setup do
    Client.reset_session()
    :ok
  end

  defp stub_qrz_lookup(opts \\ []) do
    lat = Keyword.get(opts, :lat)
    lon = Keyword.get(opts, :lon)

    lat_xml = if lat, do: "<lat>#{lat}</lat>", else: ""
    lon_xml = if lon, do: "<lon>#{lon}</lon>", else: ""

    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>
            <call>W1AW</call>
            <fname>Hiram Percy</fname>
            <name>Maxim</name>
            <addr1>225 Main St</addr1>
            <addr2>Newington</addr2>
            <state>CT</state>
            <zip>06111</zip>
            <country>United States</country>
            #{lat_xml}
            #{lon_xml}
          </Callsign>
          <Session><Key>session123</Key></Session>
        </QRZDatabase>
        """)
      end
    end)
  end

  defp stub_geocoder do
    Req.Test.expect(Geocoder, fn conn ->
      conn
      |> Plug.Conn.put_resp_content_type("application/json")
      |> Plug.Conn.send_resp(
        200,
        Jason.encode!(%{
          "status" => "OK",
          "results" => [
            %{
              "geometry" => %{
                "location" => %{"lat" => 41.714775, "lng" => -72.727260}
              }
            }
          ]
        })
      )
    end)
  end

  describe "lookup/1" do
    test "fetches from QRZ, geocodes, caches, and returns result" do
      stub_qrz_lookup()
      stub_geocoder()

      assert {:ok, result} = CallsignLocation.lookup("W1AW")
      assert result.callsign == "W1AW"
      assert_in_delta result.latitude, 41.714775, 0.001
      assert_in_delta result.longitude, -72.727260, 0.001
      assert is_binary(result.gridsquare)
      assert String.length(result.gridsquare) == 8
      assert result.name == "Hiram Percy Maxim"
      assert result.address == "225 Main St, Newington, CT, 06111, United States"

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

    test "returns cached data without calling geocoder" do
      {:ok, _} =
        %Location{}
        |> Location.changeset(%{
          callsign: "W1AW",
          latitude: 41.714,
          longitude: -72.727,
          gridsquare: "FN31pr58"
        })
        |> Repo.insert()

      assert {:ok, result} = CallsignLocation.lookup("W1AW")
      assert result.callsign == "W1AW"
      assert_in_delta result.latitude, 41.714, 0.001
      assert result.gridsquare == "FN31pr58"
    end

    test "returns error when QRZ lookup 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: INVALID</Error>
              <Key>session123</Key>
            </Session>
          </QRZDatabase>
          """)
        end
      end)

      assert {:error, _} = CallsignLocation.lookup("INVALID")
    end

    test "uses QRZ lat/lon when available instead of geocoding" do
      stub_qrz_lookup(lat: 33.229, lon: -96.458)

      assert {:ok, result} = CallsignLocation.lookup("W1AW")
      assert result.callsign == "W1AW"
      assert_in_delta result.latitude, 33.229, 0.001
      assert_in_delta result.longitude, -96.458, 0.001
      assert is_binary(result.gridsquare)
      assert String.length(result.gridsquare) == 8
      assert result.name == "Hiram Percy Maxim"
      assert result.address == "225 Main St, Newington, CT, 06111, United States"
    end

    test "falls back to geocoding when QRZ has no lat/lon" do
      stub_qrz_lookup()
      stub_geocoder()

      assert {:ok, result} = CallsignLocation.lookup("W1AW")
      assert_in_delta result.latitude, 41.714775, 0.001
      assert_in_delta result.longitude, -72.727260, 0.001
    end

    test "returns error when no address available" 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>
              <call>W1AW</call>
            </Callsign>
            <Session><Key>session123</Key></Session>
          </QRZDatabase>
          """)
        end
      end)

      assert {:error, "No address available for callsign"} = CallsignLocation.lookup("W1AW")
    end
  end
end
