defmodule WhoNeedHelpWeb.MobileTrackingControllerTest do
  use WhoNeedHelpWeb.ConnCase, async: false

  import WhoNeedHelp.AccountsFixtures

  alias WhoNeedHelp.{Accounts.Scope, Catalog, Help, Repo, Tracking}
  alias WhoNeedHelp.Tracking.{Position, TrackingSession}

  setup %{conn: conn} do
    requester = user_fixture(display_name: "Requester")
    helper = user_fixture(display_name: "Helper")
    requester_scope = Scope.for_user(requester)
    helper_scope = Scope.for_user(helper)
    category = Catalog.seed_defaults()

    {:ok, request} = Help.create_request(requester_scope, request_attrs(category))
    {:ok, assignment} = Help.accept_request(helper_scope, request.id)

    %{
      assignment: assignment,
      helper: helper,
      helper_scope: helper_scope,
      logged_in_conn: log_in_user(conn, helper)
    }
  end

  test "an authenticated participant updates and stops native tracking", context do
    {:ok, session} = Tracking.start_session(context.helper_scope, context.assignment)
    {conn, csrf_token} = csrf_connection(context.logged_in_conn, ~p"/requests")

    conn =
      json_post(
        conn,
        ~p"/mobile/tracking/#{context.assignment.id}/position",
        csrf_token,
        %{
          latitude: 50.4501,
          longitude: 30.5234,
          accuracy_meters: 7.5
        }
      )

    assert response(conn, 204) == ""
    assert %Position{} = Repo.get_by(Position, tracking_session_id: session.id)

    conn =
      conn
      |> recycle()
      |> json_post(
        ~p"/mobile/tracking/#{context.assignment.id}/stop",
        csrf_token,
        %{}
      )

    assert response(conn, 204) == ""
    refute Repo.get_by(Position, tracking_session_id: session.id)
    refute Repo.get_by(TrackingSession, id: session.id, active: true)
  end

  test "logging out removes the active native position from the server", context do
    {:ok, session} = Tracking.start_session(context.helper_scope, context.assignment)

    {:ok, _position} =
      Tracking.update_position(context.helper_scope, context.assignment, %{
        "latitude" => 50.4501,
        "longitude" => 30.5234,
        "accuracy_meters" => 7.5
      })

    conn = delete(context.logged_in_conn, ~p"/users/log-out")

    assert redirected_to(conn) == ~p"/"
    refute Repo.get_by(Position, tracking_session_id: session.id)
    refute Repo.get_by(TrackingSession, id: session.id, active: true)
  end

  test "an authenticated non-participant cannot update tracking", context do
    outsider = user_fixture(display_name: "Outsider")
    {conn, csrf_token} = csrf_connection(log_in_user(build_conn(), outsider), ~p"/requests")

    conn =
      json_post(
        conn,
        ~p"/mobile/tracking/#{context.assignment.id}/position",
        csrf_token,
        %{latitude: 50.4501, longitude: 30.5234, accuracy_meters: 7.5}
      )

    assert response(conn, 403) == ""
  end

  test "an anonymous request with a valid CSRF token remains unauthorized",
       %{conn: conn} =
         context do
    {conn, csrf_token} = csrf_connection(conn, ~p"/")

    conn =
      json_post(
        conn,
        ~p"/mobile/tracking/#{context.assignment.id}/position",
        csrf_token,
        %{latitude: 50.4501, longitude: 30.5234, accuracy_meters: 7.5}
      )

    assert response(conn, 401) == ""
  end

  defp csrf_connection(conn, path) do
    conn = get(conn, path)
    html = html_response(conn, 200)
    [_, token] = Regex.run(~r/<meta name="csrf-token" content="([^"]+)"/, html)

    {recycle(conn), token}
  end

  defp json_post(conn, path, csrf_token, body) do
    conn
    |> put_req_header("accept", "application/json")
    |> put_req_header("content-type", "application/json")
    |> put_req_header("x-csrf-token", csrf_token)
    |> post(path, Jason.encode!(body))
  end

  defp request_attrs(category) do
    %{
      "title" => "Medicine is ready at the pharmacy",
      "description" => "The medicine is reserved and paid for.",
      "pickup_instructions" => "Ask for order WNH.",
      "location_label" => "Central district",
      "latitude" => "50.4501",
      "longitude" => "30.5234",
      "urgency" => "now",
      "location_visibility" => "approximate_public",
      "structured_data" => %{"pickup_status" => "reserved"},
      "expires_at" => DateTime.utc_now(:second) |> DateTime.add(3, :hour),
      "category_id" => category.id,
      "safety_confirmed" => true
    }
  end
end
