module Users
  class DossiersController < UserController
    include DossierHelper

    layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]

    ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all]
    ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire]

    before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
    before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
    before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update]
    before_action :forbid_invite_submission!, only: [:update_brouillon]
    before_action :forbid_closed_submission!, only: [:update_brouillon]
    before_action :show_demarche_en_test_banner
    before_action :store_user_location!, only: :new

    def index
      @user_dossiers = current_user.dossiers.includes(:procedure).order_by_updated_at.page(page)
      @dossiers_invites = current_user.dossiers_invites.includes(:procedure).order_by_updated_at.page(page)
      @dossiers_supprimes = current_user.deleted_dossiers.order_by_updated_at.page(page)
      @dossier_transfers = DossierTransfer
        .includes(dossiers: :user)
        .with_dossiers
        .where(email: current_user.email)
        .page(page)
      @statut = statut(@user_dossiers, @dossiers_invites, @dossiers_supprimes, @dossier_transfers, params[:statut])
    end

    def show
      if dossier.brouillon?
        redirect_to brouillon_dossier_path(dossier)
        return
      end

      @dossier = dossier
      respond_to do |format|
        format.pdf do
          @include_infos_administration = false
          render(template: 'dossiers/show', formats: [:pdf])
        end
        format.all
      end
    end

    def demande
      @dossier = dossier
    end

    def messagerie
      @dossier = dossier
      @commentaire = Commentaire.new
    end

    def attestation
      if dossier.attestation&.pdf&.attached?
        redirect_to dossier.attestation.pdf.service_url
      else
        flash.notice = t('.no_longer_available')
        redirect_to dossier_path(dossier)
      end
    end

    def identite
      @dossier = dossier
      @user = current_user
    end

    def update_identite
      @dossier = dossier

      if @dossier.individual.update(individual_params)
        @dossier.update!(autorisation_donnees: true, identity_updated_at: Time.zone.now)
        flash.notice = t('.identity_saved')

        redirect_to brouillon_dossier_path(@dossier)
      else
        flash.now.alert = @dossier.individual.errors.full_messages
        render :identite
      end
    end

    def siret
      @dossier = dossier
    end

    def update_siret
      @dossier = dossier

      # We use the user as the holder model object for the siret value
      # (so that we can restore it on the form in case of error).
      #
      # This is the only remaining use of User#siret: it could be refactored away.
      # However some existing users have a siret but no associated etablissement,
      # so we would need to analyze the legacy data and decide what to do with it.
      current_user.siret = siret_params[:siret]

      siret_model = Siret.new(siret: siret_params[:siret])
      if !siret_model.valid?
        return render_siret_error(siret_model.errors.full_messages)
      end

      sanitized_siret = siret_model.siret
      begin
        etablissement = APIEntrepriseService.create_etablissement(@dossier, sanitized_siret, current_user.id)
      rescue APIEntreprise::API::Error::RequestFailed, APIEntreprise::API::Error::BadGateway, APIEntreprise::API::Error::TimedOut
        return render_siret_error(t('errors.messages.siret_network_error'))
      end
      if etablissement.nil?
        return render_siret_error(t('errors.messages.siret_unknown'))
      end

      current_user.update!(siret: sanitized_siret)
      @dossier.update!(autorisation_donnees: true)

      redirect_to etablissement_dossier_path
    end

    def etablissement
      @dossier = dossier

      # Redirect if the user attempts to access the page URL directly
      if !@dossier.etablissement
        flash.alert = t('.no_establishment')
        return redirect_to siret_dossier_path(@dossier)
      end
    end

    def brouillon
      @dossier = dossier_with_champs

      # TODO: remove when the champs are unifed
      if !@dossier.autorisation_donnees
        if dossier.procedure.for_individual
          redirect_to identite_dossier_path(@dossier)
        else
          redirect_to siret_dossier_path(@dossier)
        end
      end
    end

    # FIXME:
    # - delegate draft save logic to champ ?
    def update_brouillon
      @dossier = dossier_with_champs

      errors = update_dossier_and_compute_errors

      if passage_en_construction? && errors.blank?
        @dossier.passer_en_construction!
        NotificationMailer.send_en_construction_notification(@dossier).deliver_later
        @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
          DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later
        end
        return redirect_to(merci_dossier_path(@dossier))
      elsif errors.present?
        flash.now.alert = errors
      else
        flash.now.notice = t('.draft_saved')
      end

      respond_to do |format|
        format.html { render :brouillon }
        format.js { render :brouillon }
      end
    end

    def extend_conservation
      dossier.update(conservation_extension: dossier.conservation_extension + 1.month)
      flash[:notice] = t('.archived_dossier')
      redirect_to dossier_path(@dossier)
    end

    def modifier
      @dossier = dossier_with_champs
    end

    def update
      @dossier = dossier_with_champs

      errors = update_dossier_and_compute_errors

      if errors.present?
        flash.now.alert = errors
        render :modifier
      else
        redirect_to demande_dossier_path(@dossier)
      end
    end

    def merci
      @dossier = current_user.dossiers.includes(:procedure).find(params[:id])
    end

    def create_commentaire
      @commentaire = CommentaireService.build(current_user, dossier, commentaire_params)

      if @commentaire.save
        @commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
        dossier.followers_instructeurs
          .with_instant_email_message_notifications
          .each do |instructeur|
          DossierMailer.notify_new_commentaire_to_instructeur(dossier, instructeur.email).deliver_later
        end
        flash.notice = t('.message_send')
        redirect_to messagerie_dossier_path(dossier)
      else
        flash.now.alert = @commentaire.errors.full_messages
        render :messagerie
      end
    end

    def ask_deletion
      dossier = current_user.dossiers.includes(:user, procedure: :administrateurs).find(params[:id])

      if dossier.can_be_deleted_by_user?
        dossier.discard_and_keep_track!(current_user, :user_request)
        flash.notice = t('.deleted_dossier')
        redirect_to dossiers_path
      else
        flash.notice = t('.undergoingreview')
        redirect_to dossier_path(dossier)
      end
    end

    def recherche
      @search_terms = params[:q]
      return redirect_to dossiers_path if @search_terms.blank?

      @dossiers = DossierSearchService.matching_dossiers_for_user(@search_terms, current_user).page(page)

      if @dossiers.present?
        # we need the page condition when accessing page n with n>1 when the page has only 1 result
        # in order to avoid an unpleasant redirection when changing page
        if @dossiers.count == 1 && page == 1
          redirect_to url_for_dossier(@dossiers.first)
        else
          render :index
        end
      else
        flash.alert = "Vous n’avez pas de dossiers contenant « #{@search_terms} »."
        redirect_to dossiers_path
      end
    end

    def new
      erase_user_location!

      begin
        procedure = if params[:brouillon]
          Procedure.publiees.or(Procedure.brouillons).find(params[:procedure_id])
        else
          Procedure.publiees.find(params[:procedure_id])
        end
      rescue ActiveRecord::RecordNotFound
        flash.alert = t('errors.messages.procedure_not_found')
        return redirect_to dossiers_path
      end

      dossier = Dossier.new(
        revision: params[:brouillon] ? procedure.draft_revision : procedure.active_revision,
        groupe_instructeur: procedure.defaut_groupe_instructeur_for_new_dossier,
        user: current_user,
        state: Dossier.states.fetch(:brouillon)
      )
      dossier.build_default_individual
      dossier.save!

      if dossier.procedure.for_individual
        redirect_to identite_dossier_path(dossier)
      else
        redirect_to siret_dossier_path(id: dossier.id)
      end
    end

    def dossier_for_help
      dossier_id = params[:id] || params[:dossier_id]
      @dossier || (dossier_id.present? && Dossier.find_by(id: dossier_id.to_i))
    end

    def transferer
      @transfer = DossierTransfer.new(dossiers: [dossier])
    end

    def transferer_all
      @transfer = DossierTransfer.new(dossiers: current_user.dossiers)
    end

    private

    # if the status tab is filled, then this tab
    # else first filled tab
    # else mes-dossiers
    def statut(mes_dossiers, dossiers_invites, dossiers_supprimes, dossier_transfers, params_statut)
      tabs = {
        'mes-dossiers' => mes_dossiers.present?,
        'dossiers-invites' => dossiers_invites.present?,
        'dossiers-supprimes' => dossiers_supprimes.present?,
        'dossiers-transferes' => dossier_transfers.present?
      }
      if tabs[params_statut]
        params_statut
      else
        tabs
          .filter { |_tab, filled| filled }
          .map { |tab, _| tab }
          .first || 'mes-dossiers'
      end
    end

    def store_user_location!
      store_location_for(:user, request.fullpath)
    end

    def erase_user_location!
      clear_stored_location_for(:user)
    end

    def show_demarche_en_test_banner
      if @dossier.present? && @dossier.revision.draft?
        flash.now.alert = t('users.dossiers.test_procedure')
      end
    end

    def ensure_dossier_can_be_updated
      if !dossier.can_be_updated_by_user?
        flash.alert = t('users.dossiers.no_longer_editable')
        redirect_to dossiers_path
      end
    end

    def page
      [params[:page].to_i, 1].max
    end

    # FIXME: require(:dossier) when all the champs are united
    def champs_params
      params.permit(dossier: {
        champs_attributes: [
          :id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: [],
          champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: []]
        ]
      })
    end

    def dossier
      @dossier ||= Dossier.find(params[:id] || params[:dossier_id])
    end

    def dossier_with_champs
      Dossier.with_champs.find(params[:id])
    end

    def change_groupe_instructeur?
      if params[:dossier].key?(:groupe_instructeur_id)
        groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
        if groupe_instructeur_id.nil?
          @dossier.groupe_instructeur_id.present?
        else
          @dossier.groupe_instructeur_id != groupe_instructeur_id.to_i
        end
      end
    end

    def groupe_instructeur_from_params
      groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
      if groupe_instructeur_id.present?
        @dossier.procedure.groupe_instructeurs.find(groupe_instructeur_id)
      end
    end

    def update_dossier_and_compute_errors
      errors = []

      if champs_params[:dossier]
        @dossier.assign_attributes(champs_params[:dossier])
        # FIXME: in some cases a removed repetition bloc row is submitted.
        # In this case it will be treated as a new record, and the action will fail.
        @dossier.champs.filter(&:repetition?).each do |champ|
          champ.champs = champ.champs.filter(&:persisted?)
        end
        if @dossier.champs.any?(&:changed_for_autosave?)
          @dossier.last_champ_updated_at = Time.zone.now
        end

        if !@dossier.save(**validation_options)
          errors += @dossier.errors.full_messages
        elsif change_groupe_instructeur?
          @dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
        end
      end

      if !save_draft?
        errors += @dossier.check_mandatory_champs

        if @dossier.groupe_instructeur.nil?
          errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
        end
      end

      errors
    end

    def ensure_ownership!
      if !current_user.owns?(dossier)
        forbidden!
      end
    end

    def ensure_ownership_or_invitation!
      if !current_user.owns_or_invite?(dossier)
        forbidden!
      end
    end

    def forbid_invite_submission!
      if passage_en_construction? && !current_user.owns?(dossier)
        forbidden!
      end
    end

    def forbid_closed_submission!
      if passage_en_construction? && !dossier.can_transition_to_en_construction?
        forbidden!
      end
    end

    def forbidden!
      flash[:alert] = t('users.dossiers.no_access')
      redirect_to root_path
    end

    def render_siret_error(error_message)
      flash.alert = error_message
      render :siret
    end

    def individual_params
      params.require(:individual).permit(:gender, :nom, :prenom, :birthdate)
    end

    def siret_params
      params.require(:user).permit(:siret)
    end

    def commentaire_params
      params.require(:commentaire).permit(:body, :piece_jointe)
    end

    def passage_en_construction?
      dossier.brouillon? && !save_draft?
    end

    def save_draft?
      dossier.brouillon? && !params[:submit_draft]
    end

    def validation_options
      if save_draft?
        { context: :brouillon }
      else
        # rubocop:disable Lint/BooleanSymbol
        # Force ActiveRecord to re-validate associated records.
        { context: :false }
        # rubocop:enable Lint/BooleanSymbol
      end
    end
  end
end