require "test_helper"

class OidcDeviceCodeTest < ActiveSupport::TestCase
  def setup
    @application = Application.create!(
      name: "Device Code Model Test",
      slug: "device-code-model-test",
      app_type: "oidc",
      is_public_client: true,
      active: true
    )
    @user = User.create!(email_address: "device_model@example.com", password: "password123")
  end

  test "generates an opaque device_code stored as HMAC and looked up by plaintext" do
    dc = OidcDeviceCode.create!(application: @application)

    assert dc.plaintext_device_code.present?
    assert dc.device_code_hmac.present?
    assert_not_equal dc.plaintext_device_code, dc.device_code_hmac
    assert_equal dc, OidcDeviceCode.find_by_plaintext_device_code(dc.plaintext_device_code)
    assert_nil OidcDeviceCode.find_by_plaintext_device_code("wrong")
  end

  test "generates a short user_code from the unambiguous alphabet" do
    dc = OidcDeviceCode.create!(application: @application)

    assert_equal 8, dc.user_code.length
    # No visually ambiguous characters (0/O, 1/I) and only the allowed alphabet.
    assert_match(/\A[A-HJ-NP-Z2-9]{8}\z/, dc.user_code)
  end

  test "find_by_user_code normalizes case, hyphens, and whitespace" do
    dc = OidcDeviceCode.create!(application: @application)
    formatted = "#{dc.user_code[0, 4]}-#{dc.user_code[4, 4]}".downcase

    assert_equal dc, OidcDeviceCode.find_by_user_code(formatted)
    assert_equal dc, OidcDeviceCode.find_by_user_code(" #{dc.user_code} ")
    assert_nil OidcDeviceCode.find_by_user_code("nope")
  end

  test "user_code is unique" do
    dc = OidcDeviceCode.create!(application: @application)
    dup = OidcDeviceCode.new(application: @application, user_code: dc.user_code)

    assert_not dup.valid?
    assert_includes dup.errors[:user_code], "has already been taken"
  end

  test "starts pending and approve! attaches the user and auth context" do
    dc = OidcDeviceCode.create!(application: @application)
    assert dc.pending?

    dc.approve!(user: @user, acr: "1", auth_time: 1_700_000_000)

    assert dc.approved?
    assert_equal @user, dc.user
    assert_equal "1", dc.acr
    assert_equal 1_700_000_000, dc.auth_time
  end

  test "deny! marks the code denied" do
    dc = OidcDeviceCode.create!(application: @application)
    dc.deny!
    assert dc.denied?
  end

  test "expired? reflects expires_at" do
    assert OidcDeviceCode.create!(application: @application, expires_at: 1.minute.ago).expired?
    assert_not OidcDeviceCode.create!(application: @application).expired?
  end

  test "uses_pkce? and rejects malformed code_challenge" do
    assert_not OidcDeviceCode.create!(application: @application).uses_pkce?

    valid_challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
    dc = OidcDeviceCode.create!(application: @application, code_challenge: valid_challenge, code_challenge_method: "S256")
    assert dc.uses_pkce?

    bad = OidcDeviceCode.new(application: @application, code_challenge: "too-short")
    assert_not bad.valid?
    assert_includes bad.errors[:code_challenge], "must be 43-128 characters of base64url encoding"
  end
end
