defmodule Microwaveprop.Workers.MechanismClassifyWorkerTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Radio.ContactCommonVolumeRadar
  alias Microwaveprop.Workers.MechanismClassifyWorker

  defp create_contact(attrs \\ %{}) do
    default = %{
      station1: "W5AA",
      station2: "K5TR",
      qso_timestamp: ~U[2024-08-15 19:00:00Z],
      mode: "CW",
      band: Decimal.new("10000"),
      grid1: "EM12",
      grid2: "EM00",
      pos1: %{"lat" => 32.9, "lon" => -97.0},
      pos2: %{"lat" => 30.3, "lon" => -97.7},
      distance_km: Decimal.new("295")
    }

    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(Map.merge(default, attrs))
      |> Repo.insert()

    contact
  end

  defp create_radar(contact, attrs) do
    {:ok, row} =
      %ContactCommonVolumeRadar{}
      |> ContactCommonVolumeRadar.changeset(
        Map.merge(
          %{
            contact_id: contact.id,
            observed_at: DateTime.to_naive(contact.qso_timestamp),
            max_dbz: 42.0,
            mean_dbz: 28.0,
            pixel_count: 100,
            rain_pixel_count: 80,
            heavy_rain_pixel_count: 15,
            core_pixel_count: 3,
            coverage_pct: 85.0,
            common_volume_km2: 1_200.0
          },
          attrs
        )
      )
      |> Repo.insert()

    row
  end

  describe "perform/1" do
    test "classifies a 10 GHz contact with heavy rain in CV as :rain_scatter" do
      contact = create_contact()
      create_radar(contact, %{})

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      assert reloaded.propagation_mechanism == "rain_scatter"
      assert reloaded.propagation_mechanism_status == :complete
      assert reloaded.propagation_mechanism_confidence == :high
    end

    test "classifies a plain 10 GHz short-path contact with no enrichment as :troposcatter" do
      contact = create_contact()

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      assert reloaded.propagation_mechanism == "troposcatter"
      assert reloaded.propagation_mechanism_status == :complete
      assert reloaded.propagation_mechanism_confidence == :low
    end

    test "trusts user_declared_prop_mode='EME' over physics" do
      contact =
        create_contact(%{
          user_declared_prop_mode: "EME",
          band: Decimal.new("1296"),
          distance_km: Decimal.new("400")
        })

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      assert reloaded.propagation_mechanism == "eme"
      assert reloaded.propagation_mechanism_confidence == :high
    end

    test "short 5 km path classifies as :line_of_sight" do
      contact =
        create_contact(%{
          pos1: %{"lat" => 32.90, "lon" => -97.0},
          pos2: %{"lat" => 32.93, "lon" => -97.0},
          distance_km: Decimal.new("5")
        })

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      assert reloaded.propagation_mechanism == "line_of_sight"
    end

    test "contact without positions is marked unavailable, not failed" do
      contact = create_contact(%{pos1: nil, pos2: nil})

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      assert reloaded.propagation_mechanism_status == :unavailable
    end

    test "missing contact (deleted) returns :ok gracefully" do
      fake_id = Ecto.UUID.generate()

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => fake_id}
               })
    end

    test "meteor-shower calendar matches across year boundaries" do
      # Quadrantids peak Jan 4, ±3 day window. A 6 m 900 km contact on
      # Dec 31 2025 is 4 days before the Jan 4 2026 peak — should still
      # pass through shower-active matching (though classification may
      # be meteor_scatter only if other mechanisms don't fire first).
      # We assert indirectly: Dec 31 6m 900 km with no duct, no foEs
      # classifies as :meteor_scatter when the shower window handles
      # year-crossing correctly, or :troposcatter when it doesn't.
      contact =
        create_contact(%{
          qso_timestamp: ~U[2025-12-31 10:00:00Z],
          band: Decimal.new("50"),
          distance_km: Decimal.new("900"),
          station1: "W5YR"
        })

      assert :ok =
               MechanismClassifyWorker.perform(%Oban.Job{
                 args: %{"contact_id" => contact.id}
               })

      reloaded = Repo.get!(Contact, contact.id)
      # Dec 31 is 4 days from Jan 4 — outside the ±3 day window, so we
      # should NOT classify as meteor scatter (Quadrantids) here. This
      # proves the window math isn't accidentally matching everything.
      refute reloaded.propagation_mechanism == "meteor_scatter"
    end
  end
end
