class OidcAuthorizationCode < ApplicationRecord
  belongs_to :application
  belongs_to :user
  has_many :oidc_access_tokens
  has_many :oidc_refresh_tokens

  attr_accessor :plaintext_code

  before_validation :generate_code, on: :create
  before_validation :set_expiry, on: :create

  validates :code_hmac, presence: true, uniqueness: true
  validates :redirect_uri, presence: true
  validates :code_challenge_method, inclusion: {in: %w[S256], allow_nil: true}
  validate :validate_code_challenge_format, if: -> { code_challenge.present? }

  scope :valid, -> { where(used: false).where("expires_at > ?", Time.current) }
  scope :expired, -> { where("expires_at <= ?", Time.current) }

  # Find authorization code by plaintext code using HMAC verification
  def self.find_by_plaintext(plaintext_code)
    return nil if plaintext_code.blank?

    code_hmac = compute_code_hmac(plaintext_code)
    find_by(code_hmac: code_hmac)
  end

  # Compute HMAC for code lookup
  def self.compute_code_hmac(plaintext_code)
    OpenSSL::HMAC.hexdigest("SHA256", TokenHmac::KEY, plaintext_code)
  end

  def expired?
    expires_at <= Time.current
  end

  def usable?
    !used? && !expired?
  end

  def consume!
    update!(used: true)
  end

  def uses_pkce?
    code_challenge.present?
  end

  # Parse claims_requests JSON field
  def parsed_claims_requests
    return {} if claims_requests.blank?
    claims_requests.is_a?(Hash) ? claims_requests : {}
  end

  private

  def generate_code
    # Generate random plaintext code
    self.plaintext_code ||= SecureRandom.urlsafe_base64(32)
    # Store HMAC in database (not plaintext)
    self.code_hmac ||= self.class.compute_code_hmac(plaintext_code)
  end

  def set_expiry
    self.expires_at ||= 10.minutes.from_now
  end

  def validate_code_challenge_format
    # PKCE code challenge should be base64url-encoded, 43-128 characters
    unless code_challenge.match?(/\A[A-Za-z0-9\-_]{43,128}\z/)
      errors.add(:code_challenge, "must be 43-128 characters of base64url encoding")
    end
  end
end
