require "minitest/autorun"
require_relative "support/stubs"
require_relative "../lib/geocode"

# Tests for the pure helper methods in Geocode that do not require a DB
# connection or network access. All DB/Http calls are stubbed out via
# test/support/stubs.rb.

class TestGeocodeCleanForGeocode < Minitest::Test
  def test_removes_ct_reference_with_colon
    input  = "123 Main St (CT: 12345/678)"
    result = Geocode.clean_for_geocode(input)
    assert_equal "123 Main St", result
  end

  def test_removes_ct_reference_with_space
    input  = "45 High St (CT 99/100)"
    result = Geocode.clean_for_geocode(input)
    assert_equal "45 High St", result
  end

  def test_collapses_whitespace
    result = Geocode.clean_for_geocode("12  George   St")
    assert_equal "12 George St", result
  end

  def test_strips_leading_and_trailing_whitespace
    result = Geocode.clean_for_geocode("  4 River Rd  ")
    assert_equal "4 River Rd", result
  end

  def test_plain_address_unchanged
    result = Geocode.clean_for_geocode("7 Beach Rd, Hobart")
    assert_equal "7 Beach Rd, Hobart", result
  end

  def test_empty_string
    assert_equal "", Geocode.clean_for_geocode("")
  end
end

class TestGeocodeParseResult < Minitest::Test
  # Build a minimal Google Maps geocode result hash
  def result_fixture(components:, location: { "lat" => -42.8, "lng" => 147.3 }, location_type: "ROOFTOP", types: ["street_address"])
    {
      "types" => types,
      "address_components" => components,
      "geometry" => {
        "location"      => location,
        "location_type" => location_type
      }
    }
  end

  def test_full_address
    components = [
      { "types" => ["street_number"],            "long_name" => "42",        "short_name" => "42" },
      { "types" => ["route"],                    "long_name" => "Main Street", "short_name" => "Main St" },
      { "types" => ["locality"],                 "long_name" => "Hobart",    "short_name" => "Hobart" },
      { "types" => ["administrative_area_level_1"], "long_name" => "Tasmania", "short_name" => "TAS" },
      { "types" => ["postal_code"],              "long_name" => "7000",      "short_name" => "7000" }
    ]
    r = Geocode.parse_result(result_fixture(components: components))

    assert_equal "42 Main Street, Hobart, TAS, 7000", r[:display]
    assert_equal "42 Main Street",                    r[:street]
    assert_equal "Hobart",                            r[:locality]
    assert_equal "TAS",                               r[:state]
    assert_equal "7000",                              r[:postcode]
    assert_in_delta(-42.8, r[:lat],  0.001)
    assert_in_delta(147.3, r[:lng], 0.001)
  end

  def test_missing_street_number
    components = [
      { "types" => ["route"],      "long_name" => "Collins Street", "short_name" => "Collins St" },
      { "types" => ["locality"],   "long_name" => "Launceston",     "short_name" => "Launceston" },
      { "types" => ["administrative_area_level_1"], "long_name" => "Tasmania", "short_name" => "TAS" },
      { "types" => ["postal_code"], "long_name" => "7250",          "short_name" => "7250" }
    ]
    r = Geocode.parse_result(result_fixture(components: components))

    assert_equal "Collins Street",                    r[:street]
    assert_equal "Collins Street, Launceston, TAS, 7250", r[:display]
  end

  def test_postal_town_fallback_for_locality
    # When no "locality" component, fall back to "postal_town"
    components = [
      { "types" => ["street_number"],  "long_name" => "5",       "short_name" => "5" },
      { "types" => ["route"],          "long_name" => "Hill Rd", "short_name" => "Hill Rd" },
      { "types" => ["postal_town"],    "long_name" => "Burnie",  "short_name" => "Burnie" },
      { "types" => ["administrative_area_level_1"], "long_name" => "Tasmania", "short_name" => "TAS" },
      { "types" => ["postal_code"],    "long_name" => "7320",    "short_name" => "7320" }
    ]
    r = Geocode.parse_result(result_fixture(components: components))
    assert_equal "Burnie", r[:locality]
  end

  def test_empty_components_returns_safe_defaults
    r = Geocode.parse_result(result_fixture(components: []))
    assert_equal "", r[:street]
    assert_equal "", r[:locality]
    assert_equal "", r[:state]
    assert_equal "", r[:postcode]
    assert_equal "", r[:display]
  end
end

class TestGeocodePickBestResult < Minitest::Test
  def make_result(location_type:, types:, state: "TAS")
    {
      "types" => types,
      "address_components" => [
        { "types" => ["administrative_area_level_1"], "short_name" => state }
      ],
      "geometry" => { "location_type" => location_type }
    }
  end

  def test_prefers_rooftop_over_approximate
    rooftop    = make_result(location_type: "ROOFTOP",     types: ["street_address"])
    approx     = make_result(location_type: "APPROXIMATE", types: ["locality"])
    best = Geocode.pick_best_result([approx, rooftop])
    assert_equal "ROOFTOP", best.dig("geometry", "location_type")
  end

  def test_prefers_tas_result_over_non_tas
    tas_result = make_result(location_type: "APPROXIMATE", types: ["street_address"], state: "TAS")
    vic_result = make_result(location_type: "ROOFTOP",     types: ["street_address"], state: "VIC")
    best = Geocode.pick_best_result([vic_result, tas_result])
    tas_state = best["address_components"].find { |c| c["types"].include?("administrative_area_level_1") }&.fetch("short_name")
    assert_equal "TAS", tas_state
  end

  def test_single_result_returned_as_is
    r = make_result(location_type: "ROOFTOP", types: ["street_address"])
    assert_equal r, Geocode.pick_best_result([r])
  end
end
