# frozen_string_literal: true

module Decidim
  module Budgets
    # The data store for a Project in the Decidim::Budgets component. It stores a
    # title, description and any other useful information to render a custom project.
    class Project < Budgets::ApplicationRecord
      include Decidim::Resourceable
      include Decidim::Taxonomizable
      include Decidim::ScopableResource
      include Decidim::HasCategory
      include Decidim::HasAttachments
      include Decidim::HasAttachmentCollections
      include Decidim::HasReference
      include Decidim::Followable
      include Decidim::Comments::CommentableWithComponent
      include Decidim::Traceable
      include Decidim::Loggable
      include Decidim::Randomable
      include Decidim::Searchable
      include Decidim::TranslatableResource
      include Decidim::FilterableResource
      include Decidim::SoftDeletable

      translatable_fields :title, :description

      belongs_to :budget, foreign_key: "decidim_budgets_budget_id", class_name: "Decidim::Budgets::Budget", inverse_of: :projects
      has_one :component, through: :budget, foreign_key: "decidim_component_id", class_name: "Decidim::Component"
      has_many :line_items, class_name: "Decidim::Budgets::LineItem", foreign_key: "decidim_project_id", dependent: :destroy
      has_many :orders, through: :line_items, foreign_key: "decidim_project_id", class_name: "Decidim::Budgets::Order"

      delegate :organization, :participatory_space, :can_participate_in_space?, to: :component
      delegate :visible?, to: :budget

      alias can_participate? can_participate_in_space?

      scope :with_order, lambda { |order|
        if order.present?
          joins(:orders).where(decidim_budgets_orders: { id: order.id })
        else
          all
        end
      }
      scope :selected, -> { where.not(selected_at: nil) }
      scope :not_selected, -> { where(selected_at: nil) }

      geocoded_by :address

      scope_search_multi :with_any_status, [:selected, :not_selected]

      searchable_fields({
                          scope_id: :decidim_scope_id,
                          participatory_space: { component: :participatory_space },
                          A: :title,
                          D: :description,
                          datetime: :created_at
                        },
                        index_on_create: ->(project) { project.visible? },
                        index_on_update: ->(project) { project.visible? })

      def self.ordered_ids(ids)
        # Make sure each ID in the matching text has a "," character as their
        # delimiter. Otherwise e.g. ID 2 would match ID "26" in the original
        # array. This is why we search for match ",2," instead to get the actual
        # position for ID 2.
        concat_ids = connection.quote(",#{ids.join(",")},")
        order(Arel.sql("position(concat(',', decidim_budgets_projects.id::text, ',') in #{concat_ids})"))
      end

      def self.log_presenter_class_for(_log)
        Decidim::Budgets::AdminLog::ProjectPresenter
      end

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

      def resource_locator
        ::Decidim::ResourceLocatorPresenter.new([budget, self])
      end

      def polymorphic_resource_path(url_params)
        resource_locator.path(url_params)
      end

      def polymorphic_resource_url(url_params)
        resource_locator.url(url_params)
      end

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

      # Public: Overrides the `users_to_notify_on_comment_created` Commentable concern method.
      def users_to_notify_on_comment_created
        followers
      end

      # Public: Returns the number of times an specific project have been checked out.
      def confirmed_orders_count
        orders.finished.count
      end

      # Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
      def allow_resource_permissions?
        component.settings.resources_permissions_enabled
      end

      # Public: Checks if the project has been selected or not.
      #
      # Returns Boolean.
      def selected?
        selected_at.present?
      end

      # Public: Returns the attachment context for this record.
      def attachment_context
        :admin
      end

      ransacker :id_string do
        Arel.sql(%{cast("decidim_budgets_projects"."id" as text)})
      end

      # 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]

      ransacker :selected do
        Arel.sql(%{("decidim_budgets_projects"."selected_at")::text})
      end

      ransacker :confirmed_orders_count do
        query = <<-SQL.squish
        (
            SELECT COUNT(decidim_budgets_line_items.decidim_order_id)
            FROM decidim_budgets_line_items
            LEFT JOIN decidim_budgets_orders ON decidim_budgets_orders.id = decidim_budgets_line_items.decidim_order_id
            WHERE decidim_budgets_orders.checked_out_at IS NOT NULL
            AND decidim_budgets_projects.id = decidim_budgets_line_items.decidim_project_id
        )
        SQL
        Arel.sql(query)
      end

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

      def self.ransackable_attributes(auth_object = nil)
        base = %w(search_text description title)

        return base unless auth_object&.admin?

        base + %w(id_string id selected selected_at confirmed_orders_count)
      end

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