# frozen_string_literal: true

module Togglefy
  # Represents a feature in the Togglefy system.
  # A feature can have various attributes such as name, identifier, status, and associations with assignables.
  class Feature < (defined?(ApplicationRecord) ? ApplicationRecord : ActiveRecord::Base)
    # Enum for feature status.
    # If Rails version is 7 or higher, use the new enum syntax.
    # If Rails version is lower, use the old enum syntax.
    # @!attribute [r] status
    #   @return [Symbol] The status of the feature (:inactive or :active).
    if Rails::VERSION::MAJOR >= 7
      enum :status, i[inactive active]
    else
      enum status: i[inactive active]
    end

    # Associations
    # @!attribute [rw] feature_assignments
    #   @return [ActiveRecord::Relation] The feature assignments associated with this feature.
    has_many :feature_assignments, dependent: :destroy

    # Callbacks
    # Builds an identifier for the feature before validation if the name is present and identifier is blank.
    before_validation :build_identifier, if: proc { |f| f.name.present? && f.identifier.blank? }

    # Scopes
    # Finds features by their identifier.
    # @param identifier [Symbol, String, Array<Symbol, String>] The identifier to search for.
    # @return [ActiveRecord::Relation] The features matching the identifier.
    scope :identifier, ->(identifier) { where(identifier: identifier) }

    # Finds features by their group.
    # @param group [String] The group to search for.
    # @return [ActiveRecord::Relation] The features matching the group.
    scope :for_group, ->(group) { where(group: group) }

    # Finds features without a group.
    # @return [ActiveRecord::Relation] The features without a group.
    scope :without_group, -> { where(group: nil) }

    # Finds features by their environment.
    # @param environment [String] The environment to search for.
    # @return [ActiveRecord::Relation] The features matching the environment.
    scope :for_environment, ->(environment) { where(environment: environment) }

    # Finds features without an environment.
    # @return [ActiveRecord::Relation] The features without an environment.
    scope :without_environment, -> { where(environment: nil) }

    # Finds features by their tenant ID.
    # @param tenant_id [String] The tenant ID to search for.
    # @return [ActiveRecord::Relation] The features matching the tenant ID.
    scope :for_tenant, ->(tenant_id) { where(tenant_id: tenant_id) }

    # Finds features without a tenant.
    # @return [ActiveRecord::Relation] The features without a tenant.
    scope :without_tenant, -> { where(tenant_id: nil) }

    # Finds features with an inactive status.
    # @return [ActiveRecord::Relation] The features with an inactive status.
    scope :inactive, -> { where(status: :inactive) }

    # Finds features with an active status.
    # @return [ActiveRecord::Relation] The features with an active status.
    scope :active, -> { where(status: :active) }

    # Finds features by their status.
    # @param status [Symbol, String, Integer] The status to search
    # (:inactive || "inactive" || 0) or (:active || "active" || 1).
    # @return [ActiveRecord::Relation] The features matching the status.
    scope :with_status, ->(status) { where(status: status) }

    # Validations
    # Validates the presence and uniqueness of the name and identifier attributes.
    validates :name, :identifier, presence: true, uniqueness: true
    validates :identifier, format: {
      with: /\A[a-z]+(_[a-z0-9]+)*\z/,
      message: "must be in snake_case (lowercase letters and underscores only)"
    }

    # This method retrieves all assignables linked to the feature through feature assignments.
    # @return [ActiveRecord::Relation] The assignables associated with the feature.
    # @example
    #   feature.assignables
    #   Togglefy.feature(:super_powers).assignables
    #   Togglefy::Feature.find_by(identifier: :super_powers).assignables
    # @note This method includes all assignables, regardless of their class.
    def assignables
      feature_assignments.includes(:assignable).map(&:assignable)
    end

    # This method retrieves assignables of a specific class linked to the feature through feature assignments.
    # @param klass [String, Class] The class name or class of the assignable class.
    # @return [ActiveRecord::Relation] The assignables of the specified class associated with the feature.
    # @example
    #   feature.assignables_for_klass("User")
    #   feature.assignables_for_klass(User)
    #   Togglefy.feature(:super_powers).assignables_for_klass(User)
    #   Togglefy::Feature.find_by(identifier: :super_powers).assignables_for_klass(User)
    def assignables_for_type(klass)
      feature_assignments.includes(:assignable).where(assignable_type: klass.to_s).map(&:assignable)
    end

    private

    # Builds a unique identifier for the feature based on its name.
    # The identifier is generated by parameterizing the name with underscores.
    # @return [void]
    def build_identifier
      self.identifier = name.underscore.parameterize(separator: "_")
    end
  end
end