defmodule ToweropsWeb.Plugs.ApiAuthTest do
  use ToweropsWeb.ConnCase, async: true

  import Towerops.AccountsFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.ApiTokens
  alias ToweropsWeb.Plugs.ApiAuth

  describe "call/2 with valid authorization" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)

      {:ok, {_token, raw_token}} =
        ApiTokens.create_api_token(%{
          organization_id: organization.id,
          user_id: user.id,
          name: "Test Token"
        })

      %{
        user: user,
        organization: organization,
        raw_token: raw_token
      }
    end

    test "authenticates successfully with valid Bearer token", %{
      conn: conn,
      raw_token: raw_token,
      organization: organization,
      user: user
    } do
      conn =
        conn
        |> put_req_header("authorization", "Bearer #{raw_token}")
        |> ApiAuth.call([])

      refute conn.halted
      assert conn.assigns.current_organization_id == organization.id
      assert conn.assigns.current_user.id == user.id
      assert conn.assigns.api_authenticated == true
    end

    test "authenticates successfully with token without user_id", %{
      conn: conn,
      organization: organization
    } do
      {:ok, {_token, raw_token}} =
        ApiTokens.create_api_token(%{
          organization_id: organization.id,
          name: "Token Without User"
        })

      conn =
        conn
        |> put_req_header("authorization", "Bearer #{raw_token}")
        |> ApiAuth.call([])

      refute conn.halted
      assert conn.assigns.current_organization_id == organization.id
      assert is_nil(conn.assigns.current_user)
      assert conn.assigns.api_authenticated == true
    end
  end

  describe "call/2 with missing authorization" do
    test "returns 401 when Authorization header is missing", %{conn: conn} do
      conn = ApiAuth.call(conn, [])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
    end

    test "returns 401 when Authorization header is empty", %{conn: conn} do
      conn =
        conn
        |> put_req_header("authorization", "")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
    end
  end

  describe "call/2 with invalid authorization format" do
    test "returns 401 when Authorization header doesn't start with 'Bearer '", %{conn: conn} do
      conn =
        conn
        |> put_req_header("authorization", "Basic dGVzdDp0ZXN0")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
    end

    test "returns 401 when Bearer token is malformed", %{conn: conn} do
      conn =
        conn
        |> put_req_header("authorization", "Bearertoken123")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
    end

    test "returns 401 when authorization has only 'Bearer' with no token", %{conn: conn} do
      conn =
        conn
        |> put_req_header("authorization", "Bearer ")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
    end
  end

  describe "call/2 with invalid token" do
    test "returns 401 for non-existent token", %{conn: conn} do
      conn =
        conn
        |> put_req_header("authorization", "Bearer towerops_invalid_token_12345")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
    end

    test "returns 401 for randomly generated token", %{conn: conn} do
      random_token = "towerops_" <> Base.encode64(:crypto.strong_rand_bytes(32))

      conn =
        conn
        |> put_req_header("authorization", "Bearer #{random_token}")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
    end
  end

  describe "call/2 with expired token" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)

      # Create an expired token (1 day in the past)
      expires_at = DateTime.add(DateTime.utc_now(), -1, :day)

      {:ok, {_token, raw_token}} =
        ApiTokens.create_api_token(%{
          organization_id: organization.id,
          user_id: user.id,
          name: "Expired Token",
          expires_at: expires_at
        })

      %{
        user: user,
        organization: organization,
        expired_token: raw_token
      }
    end

    test "returns 401 for expired token", %{conn: conn, expired_token: expired_token} do
      conn =
        conn
        |> put_req_header("authorization", "Bearer #{expired_token}")
        |> ApiAuth.call([])

      assert conn.halted
      assert conn.status == 401
      assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
    end
  end

  describe "init/1" do
    test "returns options unchanged" do
      opts = [some: :option]
      assert ApiAuth.init(opts) == opts
    end

    test "returns empty list unchanged" do
      assert ApiAuth.init([]) == []
    end
  end
end
