defmodule Towerops.Agents.AgentTokenTest do
  use Towerops.DataCase, async: true

  alias Towerops.Agents.AgentToken

  describe "build_token/3" do
    test "generates a token with 48 bytes of entropy" do
      org_id = Ecto.UUID.generate()
      {token, _changeset} = AgentToken.build_token(org_id, "Test Agent")

      # Decode the base64-encoded token
      decoded = Base.url_decode64!(token, padding: false)

      # Should be exactly 48 bytes
      assert byte_size(decoded) == 48
    end

    test "returns token string and valid changeset for organization agent" do
      org_id = Ecto.UUID.generate()
      {token, changeset} = AgentToken.build_token(org_id, "My Agent")

      assert is_binary(token)
      assert changeset.valid?
      assert changeset.changes.token == token
      assert changeset.changes.name == "My Agent"
      assert changeset.changes.organization_id == org_id
      assert Ecto.Changeset.get_field(changeset, :is_cloud_poller) == false
    end

    test "sets is_cloud_poller to false by default" do
      org_id = Ecto.UUID.generate()
      {_token, changeset} = AgentToken.build_token(org_id, "Test Agent")

      assert Ecto.Changeset.get_field(changeset, :is_cloud_poller) == false
    end

    test "generates unique tokens on each call" do
      org_id = Ecto.UUID.generate()
      {token1, _} = AgentToken.build_token(org_id, "Agent 1")
      {token2, _} = AgentToken.build_token(org_id, "Agent 2")
      {token3, _} = AgentToken.build_token(org_id, "Agent 3")

      # All tokens should be different
      assert token1 != token2
      assert token2 != token3
      assert token1 != token3
    end

    test "creates cloud poller token when is_cloud_poller is true" do
      {token, changeset} = AgentToken.build_token(nil, "Cloud Poller", is_cloud_poller: true)

      assert is_binary(token)
      assert changeset.valid?
      assert changeset.changes.is_cloud_poller == true
      refute Map.has_key?(changeset.changes, :organization_id)
    end

    test "requires name" do
      org_id = Ecto.UUID.generate()
      {_token, changeset} = AgentToken.build_token(org_id, nil)

      refute changeset.valid?
      assert %{name: ["can't be blank"]} = errors_on(changeset)
    end

    test "adds error if cloud poller has organization_id" do
      org_id = Ecto.UUID.generate()
      {_token, changeset} = AgentToken.build_token(org_id, "Test", is_cloud_poller: true)

      refute changeset.valid?
      assert %{organization_id: ["must be nil for cloud pollers"]} = errors_on(changeset)
    end

    test "adds error if non-cloud poller has nil organization_id" do
      {_token, changeset} = AgentToken.build_token(nil, "Test", is_cloud_poller: false)

      refute changeset.valid?
      assert %{organization_id: ["can't be blank for non-cloud pollers"]} = errors_on(changeset)
    end

    test "token is URL-safe base64 encoded without padding" do
      org_id = Ecto.UUID.generate()
      {token, _changeset} = AgentToken.build_token(org_id, "Test Agent")

      # Should not contain padding characters
      refute String.contains?(token, "=")

      # Should only contain URL-safe base64 characters
      assert token =~ ~r/^[A-Za-z0-9_-]+$/
    end
  end

  describe "update_changeset/2" do
    test "allows updating name" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Old Name",
        organization_id: Ecto.UUID.generate()
      }

      attrs = %{name: "New Name"}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.name == "New Name"
    end

    test "allows updating allow_remote_debug" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Test Agent",
        organization_id: Ecto.UUID.generate(),
        allow_remote_debug: false
      }

      attrs = %{allow_remote_debug: true}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.allow_remote_debug == true
    end

    test "allows updating both name and allow_remote_debug" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Old Name",
        organization_id: Ecto.UUID.generate(),
        allow_remote_debug: false
      }

      attrs = %{name: "New Name", allow_remote_debug: true}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.name == "New Name"
      assert changeset.changes.allow_remote_debug == true
    end

    test "requires name" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Old Name",
        organization_id: Ecto.UUID.generate()
      }

      attrs = %{name: ""}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      refute changeset.valid?
      assert %{name: ["can't be blank"]} = errors_on(changeset)
    end

    test "ignores attempts to update token" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "original-token",
        name: "Test Agent",
        organization_id: Ecto.UUID.generate()
      }

      attrs = %{name: "New Name", token: "hacker-token"}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.name == "New Name"
      refute Map.has_key?(changeset.changes, :token)
    end

    test "ignores attempts to update organization_id" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Test Agent",
        organization_id: Ecto.UUID.generate()
      }

      new_org_id = Ecto.UUID.generate()
      attrs = %{name: "New Name", organization_id: new_org_id}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.name == "New Name"
      refute Map.has_key?(changeset.changes, :organization_id)
    end

    test "ignores attempts to update enabled" do
      agent_token = %AgentToken{
        id: Ecto.UUID.generate(),
        token: "existing-token",
        name: "Test Agent",
        organization_id: Ecto.UUID.generate(),
        enabled: true
      }

      attrs = %{name: "New Name", enabled: false}
      changeset = AgentToken.update_changeset(agent_token, attrs)

      assert changeset.valid?
      assert changeset.changes.name == "New Name"
      refute Map.has_key?(changeset.changes, :enabled)
    end
  end

  describe "verify_token/1" do
    test "returns {:ok, token} for valid binary token" do
      token = "valid-token-string"
      assert AgentToken.verify_token(token) == {:ok, token}
    end

    test "returns {:ok, token} for any binary string" do
      assert AgentToken.verify_token("abc123") == {:ok, "abc123"}
      assert AgentToken.verify_token("") == {:ok, ""}
      assert AgentToken.verify_token("with-dashes") == {:ok, "with-dashes"}
    end

    test "returns {:error, :invalid_token} for non-binary values" do
      assert AgentToken.verify_token(nil) == {:error, :invalid_token}
      assert AgentToken.verify_token(12_345) == {:error, :invalid_token}
      assert AgentToken.verify_token([]) == {:error, :invalid_token}
      assert AgentToken.verify_token(%{}) == {:error, :invalid_token}
      assert AgentToken.verify_token(:atom) == {:error, :invalid_token}
    end
  end
end
