defmodule Symbiont.APITest do
  use ExUnit.Case, async: false
  import Plug.Test
  import Plug.Conn
  import Symbiont.TestHelpers

  @moduletag :capture_log

  setup do
    tmp_dir = Path.join(System.tmp_dir!(), "symbiont_api_test_#{:rand.uniform(999_999)}")
    File.mkdir_p!(tmp_dir)

    stop_all_services()
    ensure_task_supervisor()

    Application.put_env(:symbiont, :data_dir, tmp_dir)
    Application.put_env(:symbiont, :heartbeat_interval_ms, :timer.hours(24))

    {:ok, _} = Symbiont.Ledger.start_link(data_dir: tmp_dir)
    {:ok, _} = Symbiont.Queue.start_link(data_dir: tmp_dir)
    {:ok, _} = Symbiont.Heartbeat.start_link([])

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

    %{tmp_dir: tmp_dir}
  end

  defp call_api(method, path, body \\ nil) do
    conn =
      if body do
        conn(method, path, Jason.encode!(body))
        |> put_req_header("content-type", "application/json")
      else
        conn(method, path)
      end

    Symbiont.API.call(conn, Symbiont.API.init([]))
  end

  defp json_body(conn) do
    Jason.decode!(conn.resp_body)
  end

  describe "GET /health" do
    test "returns ok" do
      conn = call_api(:get, "/health")
      assert conn.status == 200

      body = json_body(conn)
      assert body["status"] == "ok"
      assert body["runtime"] == "elixir/otp"
    end
  end

  describe "GET /status" do
    test "returns system status" do
      conn = call_api(:get, "/status")
      assert conn.status == 200

      body = json_body(conn)
      assert body["status"] == "healthy"
      assert body["runtime"] == "elixir/otp"
      assert is_integer(body["queue_size"])
      assert is_integer(body["total_calls"])
    end
  end

  describe "GET /ledger" do
    test "returns empty ledger initially" do
      conn = call_api(:get, "/ledger")
      assert conn.status == 200

      body = json_body(conn)
      assert body["entries"] == []
      assert body["count"] == 0
    end

    test "returns entries after appending" do
      Symbiont.Ledger.append(%{model: "haiku", estimated_cost_usd: 0.01, success: true})
      Process.sleep(50)

      conn = call_api(:get, "/ledger")
      body = json_body(conn)
      assert body["count"] == 1
      assert hd(body["entries"])["model"] == "haiku"
    end
  end

  describe "GET /ledger/stats" do
    test "returns aggregate stats" do
      conn = call_api(:get, "/ledger/stats")
      assert conn.status == 200

      body = json_body(conn)
      assert body["total_calls"] == 0
      assert body["total_cost_estimated_usd"] == 0.0
    end
  end

  describe "POST /queue" do
    test "enqueues a task" do
      conn = call_api(:post, "/queue", %{task: "Do something later"})
      assert conn.status == 200

      body = json_body(conn)
      assert body["status"] == "queued"
      assert is_binary(body["id"])
      assert body["position"] == 1
    end

    test "rejects empty task" do
      conn = call_api(:post, "/queue", %{task: ""})
      assert conn.status == 400

      body = json_body(conn)
      assert body["error"] =~ "missing"
    end

    test "rejects missing task field" do
      conn = call_api(:post, "/queue", %{})
      assert conn.status == 400
    end
  end

  describe "POST /task" do
    test "rejects empty task" do
      conn = call_api(:post, "/task", %{task: ""})
      assert conn.status == 400
    end

    test "rejects missing task" do
      conn = call_api(:post, "/task", %{})
      assert conn.status == 400
    end
  end

  describe "unknown routes" do
    test "returns 404" do
      conn = call_api(:get, "/nonexistent")
      assert conn.status == 404

      body = json_body(conn)
      assert body["error"] == "not found"
    end
  end
end
