defmodule Microwaveprop.Terrain.SrtmTest do
  use ExUnit.Case, async: true

  import ExUnit.CaptureLog

  alias Microwaveprop.Terrain.Srtm

  @tiles_dir Path.expand("~/srtm/tiles")
  @has_tiles File.exists?(Path.join(@tiles_dir, "N32W097.hgt"))

  # A full SRTM tile: 3601 * 3601 samples, each 2 bytes big-endian signed
  @samples 3601
  @test_elevation 150
  @synthetic_hgt :binary.copy(<<@test_elevation::signed-big-integer-size(16)>>, @samples * @samples)

  describe "tile_filename/2" do
    test "northern hemisphere, western hemisphere" do
      assert Srtm.tile_filename(32.78, -96.8) == "N32W097.hgt"
    end

    test "southern hemisphere, eastern hemisphere" do
      assert Srtm.tile_filename(-0.5, 10.3) == "S01E010.hgt"
    end

    test "equator, prime meridian boundary" do
      assert Srtm.tile_filename(0.5, -0.5) == "N00W001.hgt"
    end

    test "exact integer coordinates" do
      assert Srtm.tile_filename(32.0, -97.0) == "N32W097.hgt"
    end

    test "negative lon floor wrapping" do
      assert Srtm.tile_filename(35.2, -106.7) == "N35W107.hgt"
    end
  end

  describe "download_tile/3" do
    test "downloads and decompresses tile on success" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)

      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      gzipped = :zlib.gzip(@synthetic_hgt)

      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 200, gzipped)
      end)

      assert {:ok, path} = Srtm.download_tile(32.0, -97.0, tmp_dir)
      assert path == Path.join(tmp_dir, "N32W097.hgt")
      assert File.exists?(path)
      assert File.read!(path) == @synthetic_hgt
    end

    test "returns {:error, :not_available} on 404" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)

      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 404, "Not Found")
      end)

      capture_log(fn ->
        assert {:error, :not_available} = Srtm.download_tile(80.0, 0.0, tmp_dir)
      end)
    end

    test "returns error message on other HTTP status" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)

      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 500, "Internal Server Error")
      end)

      capture_log(fn ->
        assert {:error, "SRTM download HTTP 500"} = Srtm.download_tile(32.0, -97.0, tmp_dir)
      end)
    end
  end

  describe "lookup/3 with auto-download" do
    test "auto-downloads missing tile and returns elevation" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)

      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      gzipped = :zlib.gzip(@synthetic_hgt)

      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 200, gzipped)
      end)

      assert {:ok, @test_elevation} = Srtm.lookup(32.0, -97.0, tmp_dir, download: true)
    end

    test "returns {:error, :no_tile} without download opt" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)
      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      assert {:error, :no_tile} = Srtm.lookup(32.0, -97.0, tmp_dir)
    end

    test "returns {:error, :no_tile} when download fails" do
      tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
      File.mkdir_p!(tmp_dir)

      on_exit(fn -> File.rm_rf!(tmp_dir) end)

      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 404, "Not Found")
      end)

      capture_log(fn ->
        assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, tmp_dir, download: true)
      end)
    end
  end

  describe "lookup/3" do
    @describetag :srtm

    @tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
    test "returns elevation for Dallas area" do
      assert {:ok, elev} = Srtm.lookup(32.78, -96.8, @tiles_dir)
      # Dallas is roughly 120-200m elevation
      assert is_number(elev)
      assert elev > 100
      assert elev < 300
    end

    @tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
    test "returns {:error, :no_tile} for missing tile" do
      Req.Test.stub(Srtm, fn conn ->
        Plug.Conn.send_resp(conn, 404, "Not Found")
      end)

      capture_log(fn ->
        assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, @tiles_dir)
      end)
    end
  end

  describe "fetch_elevation_profile/6" do
    @describetag :srtm

    @tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
    test "returns profile with correct number of points" do
      # Dallas to a point within the same tile
      assert {:ok, profile} =
               Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 10)

      assert length(profile) == 11

      first = hd(profile)
      assert Map.has_key?(first, :lat)
      assert Map.has_key?(first, :lon)
      assert Map.has_key?(first, :d)
      assert Map.has_key?(first, :elev)
      assert Map.has_key?(first, :dist_km)
    end

    @tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
    test "all elevations are positive for Dallas area path" do
      assert {:ok, profile} =
               Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 8)

      Enum.each(profile, fn pt ->
        assert pt.elev > 0, "Expected positive elevation, got #{pt.elev}"
      end)
    end

    @tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
    test "first and last dist_km are correct" do
      assert {:ok, profile} =
               Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 4)

      first = hd(profile)
      last = List.last(profile)

      assert_in_delta first.dist_km, 0.0, 0.01
      assert last.dist_km > 0
    end

    test "returns zero elevation when tiles dir does not exist" do
      assert {:ok, profile} =
               Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, "/nonexistent", 4)

      assert Enum.all?(profile, fn p -> p.elev == 0 end)
    end
  end
end
