module Instructeurs
  class ProceduresController < InstructeurController
    before_action :ensure_ownership!, except: [:index]
    before_action :ensure_not_super_admin!, only: [:download_export, :exports]

    ITEMS_PER_PAGE = 100
    BATCH_SELECTION_LIMIT = 500

    def index
      all_procedures = current_instructeur
        .procedures
        .kept

      all_procedures_for_listing = all_procedures
        .with_attached_logo

      dossiers = current_instructeur.dossiers
        .joins(groupe_instructeur: :procedure)
        .where(procedures: { hidden_at: nil })

      # .uniq is much more faster than a distinct on a joint column
      procedures_dossiers_en_cours = dossiers.joins(:revision).en_cours.pluck(ProcedureRevision.arel_table[:procedure_id]).uniq

      @procedures = all_procedures.order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc)
      publiees_or_closes_with_dossiers_en_cours = all_procedures_for_listing.publiees.or(all_procedures.closes.where(id: procedures_dossiers_en_cours))
      @procedures_en_cours = publiees_or_closes_with_dossiers_en_cours.order(published_at: :desc).page(params[:page]).per(ITEMS_PER_PAGE)
      closes_with_no_dossier_en_cours = all_procedures.closes.excluding(all_procedures.closes.where(id: procedures_dossiers_en_cours))
      @procedures_closes = closes_with_no_dossier_en_cours.order(created_at: :desc).page(params[:page]).per(ITEMS_PER_PAGE)
      @procedures_draft = all_procedures_for_listing.brouillons.order(created_at: :desc).page(params[:page]).per(ITEMS_PER_PAGE)
      @procedures_en_cours_count = publiees_or_closes_with_dossiers_en_cours.count
      @procedures_draft_count = all_procedures_for_listing.brouillons.count
      @procedures_closes_count = closes_with_no_dossier_en_cours.count

      @dossiers_count_per_procedure = dossiers.by_statut('tous').group('groupe_instructeurs.procedure_id').reorder(nil).count
      @dossiers_a_suivre_count_per_procedure = dossiers.by_statut('a-suivre').group('groupe_instructeurs.procedure_id').reorder(nil).count
      @dossiers_archived_count_per_procedure = dossiers.by_statut('archives').group('groupe_instructeurs.procedure_id').count
      @dossiers_termines_count_per_procedure = dossiers.by_statut('traites').group('groupe_instructeurs.procedure_id').reorder(nil).count
      @dossiers_expirant_count_per_procedure = dossiers.by_statut('expirant').group('groupe_instructeurs.procedure_id').count
      @dossiers_supprimes_recemment_count_per_procedure = dossiers.by_statut('supprimes_recemment').group('groupe_instructeurs.procedure_id').reorder(nil).count

      groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
      @followed_dossiers_count_per_procedure = current_instructeur
        .followed_dossiers
        .joins(:groupe_instructeur)
        .en_cours
        .where(groupe_instructeur_id: groupe_ids)
        .visible_by_administration
        .group('groupe_instructeurs.procedure_id')
        .reorder(nil)
        .count

      @all_dossiers_counts = {
        t('.to_follow') => @dossiers_a_suivre_count_per_procedure.sum { |_, v| v },
        t('.followed') => @followed_dossiers_count_per_procedure.sum { |_, v| v },
        t('.processed') => @dossiers_termines_count_per_procedure.sum { |_, v| v },
        t('.all') => @dossiers_count_per_procedure.sum { |_, v| v },
        t('.dossiers_close_to_expiration') => @dossiers_expirant_count_per_procedure.sum { |_, v| v },
        t('.archived') => @dossiers_archived_count_per_procedure.sum { |_, v| v },
        t('.dossiers_supprimes_recemment') => @dossiers_supprimes_recemment_count_per_procedure.sum { |_, v| v }
      }

      @procedure_ids_en_cours_with_notifications = current_instructeur.procedure_ids_with_notifications(:en_cours)
      @procedure_ids_termines_with_notifications = current_instructeur.procedure_ids_with_notifications(:termine)
      @statut = params[:statut]
      @statut.blank? ? @statut = 'en-cours' : @statut = params[:statut]
    end

    def show
      @procedure = procedure
      # Technically, procedure_presentation already sets the attribute.
      # Setting it here to make clear that it is used by the view
      @procedure_presentation = procedure_presentation

      @current_filters = current_filters
      @displayable_fields_for_select, @displayable_fields_selected = procedure_presentation.displayable_fields_for_select
      @counts = current_instructeur
        .dossiers_count_summary(groupe_instructeur_ids)
        .symbolize_keys
      @can_download_dossiers = (@counts[:tous] + @counts[:archives]) > 0 && !instructeur_as_manager?

      dossiers = Dossier.where(groupe_instructeur_id: groupe_instructeur_ids)
      dossiers_count = @counts[statut.underscore.to_sym]

      @followed_dossiers_id = current_instructeur
        .followed_dossiers
        .en_cours
        .merge(dossiers.visible_by_administration)
        .pluck(:id)

      notifications = current_instructeur.notifications_for_groupe_instructeurs(groupe_instructeur_ids)
      @has_en_cours_notifications = notifications[:en_cours].present?
      @has_termine_notifications = notifications[:termines].present?
      @not_archived_notifications_dossier_ids = notifications[:en_cours] + notifications[:termines]

      @has_export_notification = notify_exports?
      @last_export = last_export_for(statut)

      @filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, statut, count: dossiers_count)

      page = params[:page].presence || 1

      @dossiers_count = @filtered_sorted_ids.size
      @filtered_sorted_paginated_ids = Kaminari
        .paginate_array(@filtered_sorted_ids)
        .page(page)
        .per(ITEMS_PER_PAGE)

      @projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_fields)
      @disable_checkbox_all = @projected_dossiers.all? { _1.batch_operation_id.present? }

      @batch_operations = BatchOperation.joins(:groupe_instructeurs)
        .where(groupe_instructeurs: current_instructeur.groupe_instructeurs.where(procedure_id: @procedure.id))
        .where(seen_at: nil)
        .distinct
    end

    def deleted_dossiers
      @procedure = procedure
      @deleted_dossiers = @procedure
        .deleted_dossiers
        .order(:dossier_id)
        .page params[:page]

      @a_suivre_count, @suivis_count, @traites_count, @tous_count, @archives_count, @supprimes_recemment_count, @expirant_count = current_instructeur
        .dossiers_count_summary(groupe_instructeur_ids)
        .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'archives', 'supprimes_recemment', 'expirant')
      @can_download_dossiers = (@tous_count + @archives_count) > 0 && !instructeur_as_manager?

      notifications = current_instructeur.notifications_for_groupe_instructeurs(groupe_instructeur_ids)
      @has_en_cours_notifications = notifications[:en_cours].present?
      @has_termine_notifications = notifications[:termines].present?

      @statut = 'supprime'
    end

    def update_displayed_fields
      values = params['values'].presence || []
      procedure_presentation.update_displayed_fields(values)

      redirect_back(fallback_location: instructeur_procedure_url(procedure))
    end

    def update_sort
      procedure_presentation.update_sort(params[:table], params[:column], params[:order])

      redirect_back(fallback_location: instructeur_procedure_url(procedure))
    end

    def add_filter
      if !procedure_presentation.add_filter(statut, params[:field], params[:value])
        flash.alert = procedure_presentation.errors.full_messages
      end

      redirect_back(fallback_location: instructeur_procedure_url(procedure))
    end

    def update_filter
      @statut = statut
      @procedure = procedure
      @procedure_presentation = procedure_presentation
      @field = params[:field]
    end

    def remove_filter
      procedure_presentation.remove_filter(statut, params[:field], params[:value])

      redirect_back(fallback_location: instructeur_procedure_url(procedure))
    end

    def download_export
      groupe_instructeurs = current_instructeur
        .groupe_instructeurs
        .where(procedure: procedure)

      @can_download_dossiers = current_instructeur
        .dossiers
        .visible_by_administration
        .exists?(groupe_instructeur_id: groupe_instructeur_ids) && !instructeur_as_manager?

      export = Export.find_or_create_fresh_export(export_format, groupe_instructeurs, current_instructeur, **export_options)

      @procedure = procedure
      @statut = export_options[:statut]
      @dossiers_count = export.count

      @last_export = last_export_for(@statut)

      if export.available?
        respond_to do |format|
          format.turbo_stream do
            flash.notice = t('instructeurs.procedures.export_available_html', file_format: export.format, file_url: export.file.url)
          end

          format.html do
            redirect_to url_from(export.file.url)
          end
        end
      else
        respond_to do |format|
          format.turbo_stream do
            if !params[:no_progress_notification]
              flash.notice = t('instructeurs.procedures.export_pending_html', url: exports_instructeur_procedure_path(procedure))
            end
          end
          format.html do
            redirect_to exports_instructeur_procedure_path(procedure), notice: t('instructeurs.procedures.export_pending_html', url: exports_instructeur_procedure_path(procedure))
          end
        end
      end
    end

    def polling_last_export
      @statut = statut
      @last_export = last_export_for(@statut)
      if @last_export.available?
        flash.notice = t('instructeurs.procedures.export_available_html', file_format: @last_export.format, file_url: @last_export.file.url)
      else
        flash.notice = t('instructeurs.procedures.export_pending_html', url: exports_instructeur_procedure_path(procedure))
      end
    end

    def email_notifications
      @procedure = procedure
      @assign_to = assign_tos.first
    end

    def update_email_notifications
      assign_tos.each do |assign_to|
        assign_to.update!(assign_to_params)
      end
      flash.notice = 'Vos notifications sont enregistrées.'
      redirect_to instructeur_procedure_path(procedure)
    end

    def stats
      @procedure = procedure
      @usual_traitement_time = @procedure.stats_usual_traitement_time
      @dossiers_funnel = @procedure.stats_dossiers_funnel
      @termines_states = @procedure.stats_termines_states
      @termines_by_week = @procedure.stats_termines_by_week
      @usual_traitement_time_by_month = @procedure.stats_usual_traitement_time_by_month_in_days
    end

    def exports
      @procedure = procedure
      @exports = Export.for_groupe_instructeurs(groupe_instructeur_ids).ante_chronological
      @export_templates = current_instructeur.export_templates_for(@procedure).includes(:groupe_instructeur)
      cookies.encrypted[cookies_export_key] = {
        value: DateTime.current,
        expires: Export::MAX_DUREE_GENERATION + Export::MAX_DUREE_CONSERVATION_EXPORT,
        httponly: true,
        secure: Rails.env.production?
      }

      respond_to do |format|
        format.turbo_stream
        format.html
      end
    end

    def email_usagers
      @procedure = procedure
      @bulk_messages = BulkMessage.where(procedure: procedure)
      @bulk_message = current_instructeur.bulk_messages.build
      @dossiers_without_groupe_count = procedure.dossiers.state_brouillon.for_groupe_instructeur(nil).count
    end

    def create_multiple_commentaire
      @procedure = procedure
      errors = []
      bulk_message = current_instructeur.bulk_messages.build(bulk_message_params)
      dossiers = procedure.dossiers.state_brouillon.for_groupe_instructeur(nil)
      dossiers.each do |dossier|
        commentaire = CommentaireService.create(current_instructeur, dossier, bulk_message_params.except(:targets))
        if commentaire.errors.empty?
          commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
        else
          errors << dossier.id
        end
      end

      valid_dossiers_count = dossiers.count - errors.count
      bulk_message.assign_attributes(
        dossier_count: valid_dossiers_count,
        dossier_state: Dossier.states.fetch(:brouillon),
        sent_at: Time.zone.now,
        instructeur_id: current_instructeur.id,
        procedure_id: @procedure.id
      )
      bulk_message.save!

      if errors.empty?
        flash[:notice] = "Tous les messages ont été envoyés avec succès"
      else
        flash[:alert] = "Envoi terminé. Cependant #{errors.count} messages n'ont pas été envoyés"
      end
      redirect_to instructeur_procedure_path(@procedure)
    end

    def administrateurs
      @procedure = procedure
      @administrateurs = procedure.administrateurs
    end

    private

    def assign_to_params
      params.require(:assign_to)
        .permit(:instant_expert_avis_email_notifications_enabled, :instant_email_dossier_notifications_enabled, :instant_email_message_notifications_enabled, :daily_email_notifications_enabled, :weekly_email_notifications_enabled)
    end

    def assign_tos
      @assign_tos ||= current_instructeur
        .assign_to
        .joins(:groupe_instructeur)
        .where(groupe_instructeur: { procedure_id: procedure_id })
    end

    def groupe_instructeur_ids
      @groupe_instructeur_ids ||= assign_tos
        .map(&:groupe_instructeur_id)
    end

    def statut
      @statut ||= (params[:statut].presence || 'a-suivre')
    end

    def export_format
      @export_format ||= params[:export_format].presence || export_template&.kind
    end

    def export_template
      @export_template ||= ExportTemplate.find(params[:export_template_id]) if params[:export_template_id].present?
    end

    def export_options
      @export_options ||= {
        time_span_type: params[:time_span_type],
        statut: params[:statut],
        export_template:,
        procedure_presentation: params[:statut].present? ? procedure_presentation : nil
      }.compact
    end

    def procedure_id
      params[:procedure_id]
    end

    def procedure
      Procedure
        .with_attached_logo
        .find(procedure_id)
        .tap { Sentry.set_tags(procedure: _1.id) }
    end

    def ensure_ownership!
      if !current_instructeur.procedures.include?(procedure)
        flash[:alert] = "Vous n’avez pas accès à cette démarche"
        redirect_to root_path
      end
    end

    def redirect_to_avis_if_needed
      if current_instructeur.procedures.count == 0 && current_instructeur.avis.count > 0
        redirect_to instructeur_all_avis_path
      end
    end

    def procedure_presentation
      @procedure_presentation ||= get_procedure_presentation
    end

    def get_procedure_presentation
      procedure_presentation, errors = current_instructeur.procedure_presentation_and_errors_for_procedure_id(procedure_id)
      if errors.present?
        flash[:alert] = "Votre affichage a dû être réinitialisé en raison du problème suivant : " + errors.full_messages.join(', ')
      end
      procedure_presentation
    end

    def current_filters
      @current_filters ||= procedure_presentation.filters.fetch(statut, [])
    end

    def bulk_message_params
      params.require(:bulk_message).permit(:body)
    end

    def notify_exports?
      last_seen_at = begin
                       DateTime.parse(cookies.encrypted[cookies_export_key])
                     rescue
                       nil
                     end

      scope = Export.generated.for_groupe_instructeurs(groupe_instructeur_ids)
      scope = scope.where(updated_at: last_seen_at...) if last_seen_at

      scope.exists?
    end

    def last_export_for(statut)
      Export.where(user_profile: current_instructeur, statut: statut, updated_at: 1.hour.ago..).last
    end

    def cookies_export_key
      "exports_#{@procedure.id}_seen_at"
    end
  end
end