# frozen_string_literal: true

module Gitlab
  module BackgroundMigration
    # This class takes a legacy upload and migrates it to the correct location
    class LegacyUploadMover
      include Gitlab::Utils::StrongMemoize

      attr_reader :upload, :project, :note
      attr_accessor :logger

      def initialize(upload)
        @upload = upload
        @note = Note.find_by(id: upload.model_id)
        @project = note&.project
        @logger = Gitlab::BackgroundMigration::Logger.build
      end

      def execute
        return unless upload

        if !project
          # if we don't have models associated with the upload we can not move it
          warn('Deleting upload due to model not found.')

          destroy_legacy_upload
        elsif note.is_a?(LegacyDiffNote)
          return unless move_legacy_diff_file

          migrate_upload
        elsif !legacy_file_exists?
          warn('Deleting upload due to file not found.')
          destroy_legacy_upload
        else
          migrate_upload
        end
      end

      private

      def migrate_upload
        return unless copy_upload_to_project

        add_upload_link_to_note_text
        destroy_legacy_file
        destroy_legacy_upload
      end

      # we should proceed and log whenever one upload copy fails, no matter the reasons
      # rubocop: disable Lint/RescueException
      def copy_upload_to_project
        @uploader = FileUploader.copy_to(legacy_file_uploader, project)

        logger.info(
          message: 'MigrateLegacyUploads: File copied successfully',
          old_path: legacy_file_uploader.file.path, new_path: @uploader.file.path
        )
        true
      rescue Exception => e
        warn(
          'File could not be copied to project uploads',
          file_path: legacy_file_uploader.file.path, error: e.message
        )
        false
      end
      # rubocop: enable Lint/RescueException

      def destroy_legacy_upload
        if note
          note.remove_attachment = true
          note.save
        end

        if upload.destroy
          logger.info(message: 'MigrateLegacyUploads: Upload was destroyed.', upload: upload.inspect)
        else
          warn('MigrateLegacyUploads: Upload destroy failed.')
        end
      end

      def destroy_legacy_file
        legacy_file_uploader.file.delete
      end

      def add_upload_link_to_note_text
        new_text = "#{note.note} \n #{@uploader.markdown_link}"
        # Bypass validations because old data may have invalid
        # noteable values. If we fail hard here, we may kill the
        # entire background migration, which affects a range of notes.
        note.update_attribute(:note, new_text)
      end

      def legacy_file_uploader
        strong_memoize(:legacy_file_uploader) do
          uploader = upload.build_uploader
          uploader.retrieve_from_store!(File.basename(upload.path))
          uploader
        end
      end

      def legacy_file_exists?
        legacy_file_uploader.file.exists?
      end

      # we should proceed and log whenever one upload copy fails, no matter the reasons
      # rubocop: disable Lint/RescueException
      def move_legacy_diff_file
        old_path = upload.absolute_path
        old_path_sub = '-/system/note/attachment'

        if !File.exist?(old_path) || !old_path.include?(old_path_sub)
          log_legacy_diff_note_problem(old_path)
          return false
        end

        new_path = upload.absolute_path.sub(old_path_sub, '-/system/legacy_diff_note/attachment')
        new_dir = File.dirname(new_path)
        FileUtils.mkdir_p(new_dir)

        FileUtils.mv(old_path, new_path)
      rescue Exception => e
        log_legacy_diff_note_problem(old_path, new_path, e)
        false
      end

      def warn(message, params = {})
        logger.warn(
          params.merge(message: "MigrateLegacyUploads: #{message}", upload: upload.inspect)
        )
      end

      def log_legacy_diff_note_problem(old_path, new_path = nil, error = nil)
        warn('LegacyDiffNote upload could not be moved to a new path',
          old_path: old_path, new_path: new_path, error: error&.message
        )
      end
      # rubocop: enable Lint/RescueException
    end
  end
end
