# frozen_string_literal: true

# handles the import of repository records
# requires 4 parameters:
# @sheet: the csv file with imported rows
# @mappings: mappings for columns
# @user: current_user
# @repository: the repository in which we import the items

module RepositoryImportParser
  class Importer
    IMPORT_BATCH_SIZE = 500

    def initialize(sheet, mappings, user, repository, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
      @columns = []
      @name_index = -1
      @id_index = nil
      @created_rows_count = 0
      @updated_rows_count = 0
      @header_skipped = false
      @repository = repository
      @sheet = sheet
      @rows = SpreadsheetParser.spreadsheet_enumerator(@sheet).reject { |r| r.all?(&:blank?) }
      @mappings = mappings
      @user = user
      @repository_columns = @repository.repository_columns
      @can_edit_existing_items = true # can_edit_existing_items
      @should_overwrite_with_empty_cells = true # should_overwrite_with_empty_cells
      @preview = preview
    end

    def run
      fetch_columns
      return check_for_duplicate_columns if check_for_duplicate_columns

      import_rows!
    end

    private

    def fetch_columns
      @mappings.each_with_index do |(_, value), index|
        value = value.to_s unless value.is_a?(Hash)

        case value
        when '0'
          @columns << nil
          @id_index = index
        when '-1'
          @columns << nil
          @name_index = index
        when 'do_not_import'
          @columns << nil
        else
          @columns << @repository_columns.where(data_type: Extends::REPOSITORY_IMPORTABLE_TYPES)
                                         .preload(Extends::REPOSITORY_IMPORT_COLUMN_PRELOADS)
                                         .find_by(id: value)
        end
      end
    end

    def check_for_duplicate_columns
      col_compact = @columns.compact
      if col_compact.map(&:id).uniq.length != col_compact.length
        { status: :error, total_rows_count: total_rows_count, updated_rows_count: @updated_rows_count, created_rows_count: @created_rows_count }
      end
    end

    def import_rows!
      checked_rows = []
      duplicate_ids = SpreadsheetParser.duplicate_ids(@sheet)

      @rows.each do |row|
        unless @header_skipped
          @header_skipped = true
          next
        end

        incoming_row = SpreadsheetParser.parse_row(row, @sheet, date_format: @user.settings['date_format'])
        next if incoming_row.compact.blank?

        if @id_index
          id = incoming_row[@id_index].to_s.gsub(RepositoryRow::ID_PREFIX, '')

          if id.present?
            existing_row = @repository.repository_rows.includes(repository_cells: :value).find_by(id: id)

            existing_row ||= @repository.repository_rows.new(
              id: SecureRandom.uuid,
              created_by: @user,
              last_modified_by: @user,
              import_status: 'invalid',
              import_message: I18n.t('repositories.import_records.steps.step3.status_message.not_exist', id: id.to_i)
            )
          end
        end

        if existing_row.present?
          if !@can_edit_existing_items
            existing_row.import_status = 'unchanged'
          elsif existing_row.archived
            existing_row.import_status = 'archived'
          elsif duplicate_ids.include?(existing_row.code)
            existing_row.import_status = 'duplicated'
          end

          if existing_row.import_status.present?
            checked_rows << existing_row if @preview
            next
          end
        end

        checked_rows << import_row(existing_row, incoming_row)
      end
      changes = ActiveModelSerializers::SerializableResource.new(
        checked_rows.compact,
        each_serializer: RepositoryRowImportSerializer,
        include: [:repository_cells]
      ).as_json

      { status: :ok,
        total_rows_count: total_rows_count,
        created_rows_count: @created_rows_count,
        updated_rows_count: @updated_rows_count,
        changes: changes,
        import_date: I18n.l(Time.zone.today, format: :full_date) }
    end

    def import_row(repository_row, import_row)
      @repository.transaction do
        @errors = []
        @updated = false
        repository_row_name = try_decimal_to_string(import_row[@name_index])
        if repository_row.present?
          repository_row.name = repository_row_name
        else
          repository_row = RepositoryRow.new(name: repository_row_name,
                                             repository: @repository,
                                             created_by: @user,
                                             last_modified_by: @user,
                                             import_status: 'created')
        end

        if @preview
          repository_row.validate
          repository_row.id = SecureRandom.uuid.gsub(/[a-zA-Z-]/, '') unless repository_row.id.present?  # ID required for preview with serializer
        else
          repository_row.save!
        end
        @errors << repository_row.errors.full_messages.join(',') if repository_row.errors.present?

        @updated = repository_row.changed?

        @columns.each_with_index do |column, index|
          next if column.blank?

          value = import_row[index]
          value = try_decimal_to_string(value) unless column.repository_number_value?

          cell_value = if value.present?
                         column.data_type.constantize.import_from_text(
                           value,
                           {
                             created_by: @user,
                             last_modified_by: @user,
                             repository_cell_attributes: {
                               repository_row: repository_row,
                               repository_column: column,
                               importing: true
                             }
                           },
                           @user.as_json(root: true, only: :settings).deep_symbolize_keys
                         )
                       end

          if value.present? && cell_value.nil?
            raise ActiveRecord::Rollback unless @preview

            @errors << I18n.t('activerecord.errors.models.repository_cell.incorrect_format')
            next
          end

          existing_cell = repository_row.repository_cells.find { |c| c.repository_column_id == column.id }

          existing_cell = if cell_value.nil?
                            handle_nil_cell_value(existing_cell)
                          else
                            handle_existing_cell_value(existing_cell, cell_value, repository_row)
                          end

          @updated ||= @preview ? existing_cell&.value&.changed? : existing_cell&.value&.saved_changes?
          @errors << existing_cell.value.errors.full_messages.join(',') if existing_cell&.value&.errors.present?
        end

        repository_row.import_status = if @errors.present?
                                         'invalid'
                                       elsif repository_row.import_status == 'created'
                                         @created_rows_count += 1
                                         'created'
                                       elsif @updated
                                         @updated_rows_count += 1
                                         'updated'
                                       else
                                         'unchanged'
                                       end
        repository_row.import_message = @errors.join(',').downcase if @errors.present?
        repository_row
      rescue ActiveRecord::RecordInvalid
        raise ActiveRecord::Rollback
      end
    end

    def handle_nil_cell_value(repository_cell)
      return unless repository_cell.present? && @should_overwrite_with_empty_cells

      @updated = erase_cell!(repository_cell, preview: @preview)

      repository_cell
    end

    def handle_existing_cell_value(repository_cell, cell_value, repository_row)
      if repository_cell.present?
        case cell_value
        when RepositoryStockValue
          repository_cell.value.update_data!(
            {
              amount: cell_value.amount,
              unit_item_id: cell_value.repository_stock_unit_item_id
            },
            @user,
            preview: @preview
          )
        when RepositoryListValue
          repository_list_item_id = cell_value[:repository_list_item_id]
          repository_cell.value.update_data!(repository_list_item_id, @user, preview: @preview)
        when RepositoryStatusValue
          repository_status_item_id = cell_value[:repository_status_item_id]
          repository_cell.value.update_data!(repository_status_item_id, @user, preview: @preview)
        when RepositoryTimeValue
          repository_cell.value.update_data!(
            repository_cell.value.data.change(hour: cell_value.data.hour, min: cell_value.data.min),
            @user,
            preview: @preview
          )
        else
          sanitized_cell_value_data = sanitize_cell_value_data(cell_value.data)
          repository_cell.value.update_data!(sanitized_cell_value_data, @user, preview: @preview)
        end
        repository_cell
      else
        # Create new cell

        if @preview
          repository_cell = repository_row.repository_cells.build(value: cell_value, repository_column: cell_value.repository_cell.repository_column)
          repository_cell.validate
          repository_cell.value.validate
          repository_cell.id = SecureRandom.uuid.gsub(/[a-zA-Z-]/, '') unless cell_value.repository_cell.id.present?  # ID required for preview with serializer
          return repository_cell
        else
          cell_value.repository_cell.value = cell_value
          repository_row.repository_cells << cell_value.repository_cell
          cell_value.save!
        end
        @updated ||= true
        cell_value.repository_cell
      end
    end

    def sanitize_cell_value_data(cell_value_data)
      case cell_value_data
      # when importing from .csv for:
      # repository_text_value, repository_number_value, repository_date_value, repository_date_time_value_base
      when String, Numeric, Date, Time
        cell_value_data
      when Array
        cell_value_data.filter_map do |element|
          if element.is_a?(Hash) && element.key?(:value)
            element[:value].to_s
          elsif element.is_a?(String)
            element
          end
        end
      else
        []
      end
    end

    def try_decimal_to_string(value)
      if value.is_a?(BigDecimal)
        value.frac.zero? ? value.to_i.to_s : value.to_s
      else
        value
      end
    end

    def total_rows_count
      # all rows minus header
      @rows.count - 1
    end

    def erase_cell!(repository_cell, preview: false)
      case repository_cell.value
      when RepositoryStockValue
        return false if repository_cell.value.amount.zero?

        repository_cell.value.update_data!(
          {
            amount: 0,
            unit_item_id: repository_cell.value.repository_stock_unit_item_id
          },
          @user,
          preview: preview
        )
      else
        preview ? repository_cell.to_destroy = true : repository_cell.value.destroy!
      end

      true
    end
  end
end
