# This model handles a user (subscriber) subscribing to another user (author).
# We also record the source of the subscription (Article, Comment, etc.) via a
# polymorphic association (user_subscription_source/able).
class UserSubscription < ApplicationRecord
  ALLOWED_TYPES = %w[Article].freeze

  counter_culture :subscriber, column_name: "subscribed_to_user_subscriptions_count"
  counter_culture :user_subscription_sourceable, column_name: "user_subscriptions_count"

  belongs_to :author, class_name: "User", inverse_of: :source_authored_user_subscriptions
  belongs_to :subscriber, class_name: "User", inverse_of: :subscribed_to_user_subscriptions
  belongs_to :user_subscription_sourceable, polymorphic: true, optional: true

  validates :subscriber_email, presence: true
  validates :subscriber_id, uniqueness: {
    scope: %i[subscriber_email user_subscription_sourceable_type user_subscription_sourceable_id]
  }

  validates :user_subscription_sourceable_id, presence: true, on: :create
  validates :user_subscription_sourceable_id, presence: true, on: :update, if: :user_subscription_sourceable_type
  validates :user_subscription_sourceable_type, presence: true, on: :create
  validates :user_subscription_sourceable_type, presence: true, on: :update, if: :user_subscription_sourceable_id

  validates :user_subscription_sourceable_type, inclusion: { in: ALLOWED_TYPES }, on: :create
  validates :user_subscription_sourceable_type,
            inclusion: { in: ALLOWED_TYPES }, on: :update, if: :user_subscription_sourceable_id

  validate :tag_enabled
  validate :non_apple_auth_subscriber
  validate :active_user_subscription_source

  def self.build(source:, subscriber:)
    new(build_attributes(source, subscriber))
  end

  def self.make(source:, subscriber:)
    create(build_attributes(source, subscriber))
  end

  def self.build_attributes(source, subscriber)
    {
      user_subscription_sourceable: source,
      author_id: source&.user_id,
      subscriber_id: subscriber&.id,
      subscriber_email: subscriber&.email
    }
  end

  private

  def tag_enabled
    return unless user_subscription_sourceable
    return if liquid_tags_used.include?(UserSubscriptionTag)

    errors.add(:base, I18n.t("models.user_subscription.not_enabled"))
  end

  def liquid_tags_used
    MarkdownProcessor::Parser.new(
      user_subscription_sourceable.body_markdown,
      source: user_subscription_sourceable,
      user: user_subscription_sourceable.user,
    ).tags_used
  rescue StandardError # Can occur during parsing for improper tags or bad args
    []
  end

  def non_apple_auth_subscriber
    return unless subscriber_email&.end_with?("@privaterelay.appleid.com")

    errors.add(:subscriber_email, I18n.t("models.user_subscription.non_apple"))
  end

  def active_user_subscription_source
    return unless user_subscription_sourceable

    source_active =
      # Don't create new user subscriptions for inactive sources
      # (i.e. unpublished Articles, deleted Comments, etc.)
      case user_subscription_sourceable_type
      when "Article"
        user_subscription_sourceable.published?
      when "Comment"
        !user_subscription_sourceable.deleted?
      else
        false
      end

    return if source_active

    errors.add(:base, I18n.t("models.user_subscription.source_not_found", source: user_subscription_sourceable_type))
  end
end
