# frozen_string_literal: true

class UxToolkitAttachmentStorageController < ApplicationController
  layout 'admin'

  SORT_SESSION_KEY = :ux_toolkit_attachment_storage_sort

  before_action :require_admin

  helper :attachments
  helper :ux_toolkit_attachment_storage

  SORT_COLUMNS = {
    'filename' => "#{Attachment.table_name}.filename",
    'content_type' => "#{Attachment.table_name}.content_type",
    'filesize' => "#{Attachment.table_name}.filesize",
    'created_on' => "#{Attachment.table_name}.created_on",
    'downloads' => "#{Attachment.table_name}.downloads"
  }.freeze

  def index
    @filters = filter_params
    @attachment_trash_enabled = RedmineUxToolkit::Services::AttachmentStorage.enabled?
    @projects = Project.active.order(:name).to_a
    @content_types = Attachment.where.not(content_type: [nil, '']).distinct.order(:content_type).pluck(:content_type)
    @container_types = Attachment.where.not(container_type: [nil, '']).distinct.order(:container_type).pluck(:container_type)
    apply_sort_state

    @attachment_scope = filtered_attachments
    @total_size = @attachment_scope.sum(:filesize)
    @attachment_count = @attachment_scope.count
    @attachment_pages = Paginator.new(@attachment_count, per_page_option, params[:page])
    @attachments = @attachment_scope
                   .includes(:author)
                   .offset(@attachment_pages.offset)
                   .limit(@attachment_pages.per_page)
                   .to_a
    @trash_records_by_attachment_id = UxToolkitAttachmentTrashRecord.latest_for_attachment_ids(@attachments.map(&:id))
  end

  def bulk_update
    action = params[:storage_action].to_s
    attachment_ids = params[:attachment_ids]

    result =
      case action
      when 'move_to_trash'
        RedmineUxToolkit::Services::AttachmentStorage.move_to_trash(attachment_ids, user: User.current)
      when 'restore'
        RedmineUxToolkit::Services::AttachmentStorage.restore(trash_record_ids_for(attachment_ids), user: User.current)
      when 'permanently_delete'
        RedmineUxToolkit::Services::AttachmentStorage.permanently_delete(trash_record_ids_for(attachment_ids), user: User.current)
      else
        RedmineUxToolkit::Services::AttachmentStorage::Result.new(
          status: 'failed',
          message: l(:error_ux_toolkit_attachment_storage_unknown_action),
          count: 0
        )
      end

    if result.status == 'success'
      flash[:notice] = result.message
    else
      flash[:error] = result.message
    end

    redirect_to ux_toolkit_attachment_storage_path(index_redirect_params)
  end

  private

  def filtered_attachments
    scope = Attachment.all
    scope = scope.where("#{Attachment.table_name}.filename LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(@filters[:filename])}%") if @filters[:filename].present?
    scope = scope.where(content_type: @filters[:content_type]) if @filters[:content_type].present?
    scope = scope.where(container_type: @filters[:container_type]) if @filters[:container_type].present?
    scope = scope.where("#{Attachment.table_name}.filesize >= ?", @filters[:min_size].to_i) if positive_integer?(@filters[:min_size])
    scope = scope.where("#{Attachment.table_name}.filesize <= ?", @filters[:max_size].to_i) if positive_integer?(@filters[:max_size])
    scope = scope.where("#{Attachment.table_name}.created_on <= ?", @filters[:older_than_days].to_i.days.ago) if positive_integer?(@filters[:older_than_days])
    scope = filter_by_project(scope)
    scope = filter_by_status(scope)
    scope.reorder(Arel.sql("#{SORT_COLUMNS[@sort]} #{@order.upcase}, #{Attachment.table_name}.id DESC"))
  end

  def filter_by_project(scope)
    return scope unless positive_integer?(@filters[:project_id])

    project_id = @filters[:project_id].to_i
    scope.joins(project_filter_join_sql)
         .where(
           'ux_att_issue.project_id = :project_id OR ux_att_document.project_id = :project_id OR ux_att_version.project_id = :project_id OR ux_att_project.id = :project_id OR ux_att_wiki.project_id = :project_id',
           project_id: project_id
         )
  end

  def filter_by_status(scope)
    return scope if @filters[:file_status].blank?
    return scope unless UxToolkitAttachmentTrashRecord.table_available?

    case @filters[:file_status]
    when 'active'
      excluded_ids = UxToolkitAttachmentTrashRecord.where(status: [
        UxToolkitAttachmentTrashRecord::STATUS_TRASHED,
        UxToolkitAttachmentTrashRecord::STATUS_DELETED,
        UxToolkitAttachmentTrashRecord::STATUS_MISSING
      ]).select(:attachment_id)
      scope.where.not(id: excluded_ids)
    when 'in_trash'
      scope.where(id: UxToolkitAttachmentTrashRecord.trashed.select(:attachment_id))
    when 'permanently_deleted'
      scope.where(id: UxToolkitAttachmentTrashRecord.where(status: UxToolkitAttachmentTrashRecord::STATUS_DELETED).select(:attachment_id))
    when 'missing'
      scope.where(id: UxToolkitAttachmentTrashRecord.where(status: UxToolkitAttachmentTrashRecord::STATUS_MISSING).select(:attachment_id))
    else
      scope
    end
  end

  def project_filter_join_sql
    <<~SQL.squish
      LEFT JOIN issues ux_att_issue
        ON ux_att_issue.id = #{Attachment.table_name}.container_id
       AND #{Attachment.table_name}.container_type = 'Issue'
      LEFT JOIN documents ux_att_document
        ON ux_att_document.id = #{Attachment.table_name}.container_id
       AND #{Attachment.table_name}.container_type = 'Document'
      LEFT JOIN versions ux_att_version
        ON ux_att_version.id = #{Attachment.table_name}.container_id
       AND #{Attachment.table_name}.container_type = 'Version'
      LEFT JOIN projects ux_att_project
        ON ux_att_project.id = #{Attachment.table_name}.container_id
       AND #{Attachment.table_name}.container_type = 'Project'
      LEFT JOIN wiki_pages ux_att_wiki_page
        ON ux_att_wiki_page.id = #{Attachment.table_name}.container_id
       AND #{Attachment.table_name}.container_type = 'WikiPage'
      LEFT JOIN wikis ux_att_wiki
        ON ux_att_wiki.id = ux_att_wiki_page.wiki_id
    SQL
  end

  def filter_params
    {
      filename: params[:filename].to_s.strip,
      content_type: params[:content_type].to_s,
      min_size: params[:min_size].to_s.strip,
      max_size: params[:max_size].to_s.strip,
      older_than_days: params[:older_than_days].to_s.strip,
      project_id: params[:project_id].to_s,
      container_type: params[:container_type].to_s,
      file_status: params[:file_status].to_s
    }
  end

  def apply_sort_state
    requested_sort = params[:sort].to_s
    requested_order = params[:order].to_s
    stored_sort = session[SORT_SESSION_KEY].is_a?(Hash) ? session[SORT_SESSION_KEY] : {}

    @sort =
      if SORT_COLUMNS.key?(requested_sort)
        requested_sort
      elsif SORT_COLUMNS.key?(stored_sort['sort'].to_s)
        stored_sort['sort'].to_s
      else
        'created_on'
      end

    @order =
      if %w[asc desc].include?(requested_order)
        requested_order
      elsif %w[asc desc].include?(stored_sort['order'].to_s)
        stored_sort['order'].to_s
      else
        'desc'
      end

    session[SORT_SESSION_KEY] = {'sort' => @sort, 'order' => @order}
  end

  def trash_record_ids_for(attachment_ids)
    ids = Array(attachment_ids).map(&:to_s).select {|id| id.match?(/\A\d+\z/) }
    UxToolkitAttachmentTrashRecord.trashed.where(attachment_id: ids).pluck(:id)
  end

  def index_redirect_params
    request.query_parameters.slice('filename', 'content_type', 'min_size', 'max_size', 'older_than_days', 'project_id', 'container_type', 'file_status', 'sort', 'order', 'page')
  end

  def positive_integer?(value)
    value.to_s.match?(/\A\d+\z/) && value.to_i.positive?
  end

end
