# frozen_string_literal: true

module Decidim
  module Debates
    # The data store for a Debate in the Decidim::Debates component. It stores a
    # title, description and any other useful information to render a custom
    # debate.
    class Debate < Debates::ApplicationRecord
      include Decidim::HasComponent
      include Decidim::Taxonomizable
      include Decidim::HasCategory
      include Decidim::Resourceable
      include Decidim::Followable
      include Decidim::Comments::CommentableWithComponent
      include Decidim::Comments::HasAvailabilityAttributes
      include Decidim::ScopableResource
      include Decidim::Authorable
      include Decidim::Reportable
      include Decidim::HasAttachments
      include Decidim::HasReference
      include Decidim::Traceable
      include Decidim::Loggable
      include Decidim::DownloadYourData
      include Decidim::NewsletterParticipant
      include Decidim::Searchable
      include Decidim::TranslatableResource
      include Decidim::TranslatableAttributes
      include Decidim::Likeable
      include Decidim::Randomable
      include Decidim::FilterableResource
      include Decidim::SoftDeletable

      belongs_to :last_comment_by, polymorphic: true, foreign_type: "last_comment_by_type", optional: true
      component_manifest_name "debates"

      validates :title, presence: true

      translatable_fields :title, :description, :instructions, :information_updates
      searchable_fields({
                          participatory_space: { component: :participatory_space },
                          A: :title,
                          D: :description,
                          datetime: :start_time
                        },
                        index_on_create: ->(debate) { debate.visible? },
                        index_on_update: ->(debate) { debate.visible? })

      scope :updated_at_desc, -> { order(arel_table[:updated_at].desc) }
      scope :open, -> { where(closed_at: nil) }
      scope :closed, -> { where.not(closed_at: nil).or(where(end_time: ..Time.current)) }
      scope :ongoing, -> { open.where(start_time: ..Time.current, end_time: Time.current..).or(open.where(start_time: nil, end_time: nil)) }
      scope :authored_by, ->(author) { where(author:) }
      scope :commented_by, lambda { |author|
        joins(:comments).where(
          decidim_comments_comments:
          {
            decidim_author_id: author.id,
            decidim_author_type: author.class.base_class.name
          }
        )
      }
      scope_search_multi :with_any_state, [:ongoing, :closed]

      # Returns the presenter for this debate, to be used in the views.
      # Required by ResourceRenderer.
      def presenter
        Decidim::Debates::DebatePresenter.new(self)
      end

      def self.log_presenter_class_for(_log)
        Decidim::Debates::AdminLog::DebatePresenter
      end

      def comments_start_time
        start_time
      end

      def comments_end_time
        end_time
      end

      # Public: Overrides the `reported_attributes` Reportable concern method.
      def reported_attributes
        [:title, :description]
      end

      # Public: Overrides the `reported_searchable_content_extras` Reportable concern method.
      def reported_searchable_content_extras
        [author_name]
      end

      # Public: Calculates whether the current debate is an AMA-styled one or not.
      # AMA-styled debates are those that have a start and end time set, and comments
      # are only open during that timelapse. AMA stands for Ask Me Anything, a type
      # of debate inspired by Reddit.
      #
      # Returns a Boolean.
      def ama?
        start_time.present? && end_time.present?
      end

      # Public: Checks whether the debate is an AMA-styled one and is ongoing.
      #
      # Returns a boolean.
      def ongoing_ama?
        ama? && Time.current.between?(start_time, end_time)
      end

      # Public: Checks if the debate is ongoing or not.
      #
      # Returns a boolean.
      def ongoing?
        (ama? && ongoing_ama?) || !ama?
      end

      def not_started?
        start_time.present? && start_time > Time.current
      end

      # Note that a debate can be finished even if it is not closed (meaning it has no conclusions).
      def finished?
        end_time.present? && end_time < Time.current
      end

      def state
        if closed?
          :closed
        elsif ongoing?
          :ongoing
        elsif not_started?
          :not_started
        else
          :finished
        end
      end

      # Public: Overrides the `accepts_new_comments?` CommentableWithComponent concern method.
      def accepts_new_comments?
        return false unless ongoing?
        return false if closed?

        commentable? && !comments_blocked? && comments_allowed?
      end

      # Public: Overrides the `comments_have_alignment?` Commentable concern method.
      def comments_have_alignment?
        true
      end

      # Public: Overrides the `comments_have_votes?` Commentable concern method.
      def comments_have_votes?
        true
      end

      # Public: Identifies the commentable type in the API.
      def commentable_type
        self.class.name
      end

      # Public: Checks whether the comments are displayed in a single-column layout.
      def single_column_layout?
        comments_layout == "single_column"
      end

      # Public: Checks whether the comments are displayed in a two-column layout.
      def two_columns_layout?
        comments_layout == "two_columns"
      end

      # Public: Override Commentable concern method `users_to_notify_on_comment_created`
      def users_to_notify_on_comment_created
        return Decidim::User.where(id: followers).or(Decidim::User.where(id: component.participatory_space.admins)).distinct if official?

        followers
      end

      # Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
      def allow_resource_permissions?
        true
      end

      def self.export_serializer
        Decidim::Debates::DownloadYourDataDebateSerializer
      end

      def self.newsletter_participant_ids(component)
        authors_ids = Decidim::Debates::Debate.where(component:)
                                              .where(decidim_author_type: Decidim::UserBaseEntity.name)
                                              .where.not(author: nil)
                                              .group(:decidim_author_id)
                                              .pluck(:decidim_author_id).flatten.compact
        commentators_ids = Decidim::Comments::Comment.user_commentators_ids_in(Decidim::Debates::Debate.where(component:))
        (authors_ids + commentators_ids).flatten.compact.uniq
      end

      # Checks whether the user can edit the debate.
      #
      # user - the user to check for authorship
      def editable_by?(user)
        !closed? && authored_by?(user)
      end

      # Checks whether the debate is closed or not.
      #
      def closed?
        closed_at.present? && conclusions.present?
      end

      # Checks whether the user can edit the debate.
      #
      # user - the user to check for authorship
      def closeable_by?(user)
        authored_by?(user)
      end

      # Public: Updates the comments counter cache. We have to do it these
      # way in order to properly calculate the counter with hidden
      # comments.
      #
      # rubocop:disable Rails/SkipsModelValidations
      def update_comments_count
        comments_count = comments.not_hidden.not_deleted.count
        last_comment = comments.not_hidden.not_deleted.order("created_at DESC").first

        update_columns(
          last_comment_at: last_comment&.created_at,
          last_comment_by_id: last_comment&.decidim_author_id,
          last_comment_by_type: last_comment&.decidim_author_type,
          comments_count:,
          updated_at: Time.current
        )
      end
      # rubocop:enable Rails/SkipsModelValidations

      # Create i18n ransackers for :title and :description.
      # Create the :search_text ransacker alias for searching from both of these.
      ransacker_i18n_multi :search_text, [:title, :description]

      def self.ransackable_scopes(_auth_object = nil)
        [:with_any_state, :with_any_origin, :with_any_taxonomies]
      end

      def self.ransack(params = {}, options = {})
        DebateSearch.new(self, params, options)
      end

      def self.ransackable_attributes(_auth_object = nil)
        %w(search_text title description)
      end

      def self.ransackable_associations(_auth_object = nil)
        %w(taxonomies)
      end

      private

      def comments_blocked?
        component.current_settings.comments_blocked
      end
    end
  end
end
