class RepositoryRowsController < ApplicationController
  include InputSanitizeHelper
  include ActionView::Helpers::TextHelper
  include ApplicationHelper
  include MyModulesHelper
  include RepositoryDatatableHelper
  include StorageLocationsHelper

  before_action :load_repository, except: %i(show print rows_to_print print_zpl validate_label_template_columns)
  before_action :load_repository_or_snapshot, only: %i(show print rows_to_print print_zpl
                                                       validate_label_template_columns)
  before_action :load_repository_row, only: %i(show update update_cell assigned_task_list
                                               active_reminder_repository_cells relationships)
  before_action :load_repository_rows, only: %i(print rows_to_print print_zpl validate_label_template_columns)

  before_action :check_read_permissions, except: %i(create update update_cell delete_records
                                                    copy_records archive_records restore_records)
  before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
  before_action :check_create_permissions, only: :create
  before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records)
  before_action :check_manage_permissions, only: %i(update update_cell copy_records)

  def index
    @draw = params[:draw].to_i
    per_page = params[:length] == '-1' ? 100 : params[:length].to_i
    page = (params[:start].to_i / per_page) + 1
    datatable_service = RepositoryDatatableService.new(@repository, params, current_user)

    @all_rows_count = datatable_service.all_count
    @filtered_rows_count = datatable_service.filtered_count
    @columns_mappings = datatable_service.mappings
    repository_rows = datatable_service.repository_rows
    repository_rows = repository_rows.where(archived: params[:archived]) unless @repository.archived?
    @repository_rows = repository_rows.page(page).per(per_page)
  rescue RepositoryFilters::ColumnNotFoundException
    render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.column_not_found') }
  rescue RepositoryFilters::ValueNotFoundException
    render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.value_not_found') }
  end

  def show
    respond_to do |format|
      format.html do
        redirect_to repository_path(@repository)
      end

      format.json do
        @my_module = if params[:my_module_id].present?
                       MyModule.repository_row_assignable_by_user(current_user).find_by(id: params[:my_module_id])
                     end
        return render_403 if @my_module && !can_read_my_module?(@my_module)

        if @my_module
          @my_module_assign_error = if !can_assign_my_module_repository_rows?(@my_module)
                                      I18n.t('repository_row.modal_info.assign_to_task_error.no_access')
                                    elsif @repository_row.my_modules.where(id: @my_module.id).any?
                                      I18n.t('repository_row.modal_info.assign_to_task_error.already_assigned')
                                    end
        end

        @assigned_modules = @repository_row.my_modules.distinct
        @viewable_modules = @assigned_modules.readable_by_user(current_user, current_user.teams)
                                             .joins(:my_module_repository_rows)
                                             .select('my_module_repository_rows.created_at, my_modules.*')
                                             .order(my_module_repository_rows: { created_at: :desc })
        @reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any?
      end
    end
  end

  def create
    service = RepositoryRows::CreateRepositoryRowService
              .call(repository: @repository, user: current_user, params: update_params)

    if service.succeed?
      repository_row = service.repository_row
      log_activity(:create_item_inventory, repository_row, { repository_row: repository_row.id,
                                                             repository: @repository.id })
      repository_row.repository_cells.where(value_type: 'RepositoryTextValue').each do |repository_cell|
        record_annotation_notification(repository_row, repository_cell)
      end

      if update_params[:my_module_id].present?
        my_module = MyModule.readable_by_user(current_user, current_team).find_by(id: update_params[:my_module_id])

        return render_403 unless my_module.present? && can_read_my_module?(my_module)

        ActiveRecord::Base.transaction do
          service_assign = RepositoryRows::MyModuleAssignUnassignService.call(
            my_module: my_module,
            repository: @repository,
            user: current_user,
            params: { rows_to_assign: [repository_row.id] }
          )

          render json: service_assign.errors, status: :bad_request unless service_assign.succeed?
        end
      end

      render json: { id: service.repository_row.id, flash: t('repositories.create.success_flash',
                                                             record: escape_input(repository_row.name),
                                                             repository: escape_input(@repository.name)),
                     repository_row_url: repository_repository_row_path(@repository, repository_row) },
             status: :ok
    else
      render json: service.errors, status: :bad_request
    end
  end

  def validate_label_template_columns
    label_template = LabelTemplate.where(team_id: current_team.id).find(params[:label_template_id])

    label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_rows.first).render
    if label_code[:error].empty?
      render json: { label_code: label_code[:label] }
    else
      render json: { error: label_code[:error].first, label_code: label_code[:label] }, status: :unprocessable_entity
    end
  end

  def print_zpl
    label_template = LabelTemplate.find_by(id: params[:label_template_id])
    labels = @repository_rows.flat_map do |repository_row|
      LabelTemplates::RepositoryRowService.new(label_template,
                                               repository_row).render[:label]
    end

    render(
      json: {
        html:
          render_to_string(
            partial: 'label_printers/print_zebra_progress_modal'
          ),
        labels: labels
      }
    )
  end

  def rows_to_print
    render json: @repository_rows, each_serializer: RepositoryRowSerializer, user: current_user
  end

  def print
    # reset all potential error states for printers and discard all jobs

    # rubocop:disable Rails/SkipsModelValidations
    LabelPrinter.update_all(status: :ready, current_print_job_ids: [])
    # rubocop:enable Rails/SkipsModelValidations

    label_printer = LabelPrinter.find(params[:label_printer_id])
    label_template = LabelTemplate.find_by(id: params[:label_template_id])

    job_ids = @repository_rows.flat_map do |repository_row|
      LabelPrinters::PrintJob.perform_later(
        label_printer,
        LabelTemplates::RepositoryRowService.new(label_template,
                                                 repository_row).render[:label],
        params[:copies].to_i
      ).job_id
    end

    label_printer.update!(current_print_job_ids: job_ids * params[:copies].to_i)

    render json: {
      html: render_to_string(
        partial: 'label_printers/print_progress_modal',
        locals: { starting_item_count: label_printer.current_print_job_ids.length,
                  label_printer: label_printer }
      )
    }
  end

  def update
    row_update = RepositoryRows::UpdateRepositoryRowService
                 .call(repository_row: @repository_row, user: current_user, params: update_params)

    if row_update.succeed?
      if row_update.record_updated
        log_activity(:edit_item_inventory, @repository_row, { repository_row: @repository_row.id,
                                                              repository: @repository.id })
        @repository_row.repository_cells.where(value_type: 'RepositoryTextValue').each do |repository_cell|
          record_annotation_notification(@repository_row, repository_cell)
        end
      end

      render json: {
        id: @repository_row.id,
        flash: t(
          'repositories.update.success_flash',
          record: escape_input(@repository_row.name),
          repository: escape_input(@repository.name)
        )
      }, status: :ok
    else
      render json: row_update.errors, status: :bad_request
    end
  end

  def update_cell
    return render_422(t('.invalid_params')) if
      update_params['repository_row'].present? && update_params['repository_cells'].present?
    return render_422(t('.invalid_params')) if
      update_params['repository_cells'] && update_params['repository_cells'].size != 1

    row_cell_update =
      RepositoryRows::UpdateRepositoryRowService.call(
        repository_row: @repository_row, user: current_user, params: update_params
      )

    if row_cell_update.succeed?
      if row_cell_update.record_updated
        log_activity(:edit_item_field_inventory, @repository_row,
                     { repository_row: @repository_row.id,
                       repository_column: update_params['repository_cells']&.keys&.first ||
                       I18n.t('repositories.table.row_name') })

        record_annotation_notification(@repository_row, row_cell_update.cell) if row_cell_update.cell && row_cell_update.cell.value_type == 'RepositoryTextValue'
      end
      @reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any?

      return render json: { name: @repository_row.name } if update_params['repository_row'].present?

      column = row_cell_update.column
      cell = row_cell_update.cell&.reload || row_cell_update.cell
      data = { value_type: column.data_type, id: column.id, value: nil }

      return render json: data if cell.blank?

      data['hasActiveReminders'] = @reminders_present
      data.merge! serialize_repository_cell_value(cell,
                                                  @repository.team,
                                                  @repository,
                                                  reminders_enabled: @reminders_present)
      render json: data
    else
      render json: row_cell_update.errors, status: :bad_request
    end
  end

  def delete_records
    deleted_count = 0
    if selected_params
      selected_params.each do |row_id|
        row = @repository.repository_rows.find_by(id: row_id)
        next unless row && can_manage_repository_rows?(@repository)

        log_activity(:delete_item_inventory, row, { repository_row: row.id, repository: @repository.id })
        row.destroy && deleted_count += 1
      end
      if deleted_count.zero?
        flash = t('repositories.destroy.no_deleted_records_flash',
                  other_records_number: selected_params.count)
      elsif deleted_count != selected_params.count
        not_deleted_count = selected_params.count - deleted_count
        flash = t('repositories.destroy.contains_other_records_flash',
                  records_number: deleted_count,
                  other_records_number: not_deleted_count)
      else
        flash = t('repositories.destroy.success_flash',
                  records_number: deleted_count)
      end
      color = deleted_count.zero? ? 'info' : 'success'
      render json: { flash: flash, color: color }, status: :ok
    else
      render json: {
        flash: t('repositories.destroy.no_records_selected_flash')
      }, status: :bad_request
    end
  end

  def copy_records
    duplicate_service = RepositoryActions::DuplicateRows.new(
      current_user, @repository, selected_rows_in_repo_params
    )
    duplicate_service.call
    render json: {
      flash: t('repositories.copy_records_report',
               number: duplicate_service.number_of_duplicated_items)
    }, status: :ok
  end

  def available_rows
    if @repository.repository_rows.active.blank?
      no_items_string =
        "#{t('projects.reports.new.save_PDF_to_inventory_modal.no_items')} " \
        "#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'),
                   repository_path(@repository),
                   data: { 'no-turbolink' => true })}"
      render json: { no_items: no_items_string }, status: :unprocessable_entity
    else
      render json: { results: load_available_rows }, status: :ok
    end
  end

  def assigned_task_list
    assigned_modules = @repository_row.my_modules.joins(experiment: :project)
                                      .where_attributes_like(
                                        ['my_modules.name', 'experiments.name', 'projects.name'],
                                        params[:query],
                                        whole_phrase: true
                                      )
    viewable_modules = assigned_modules.readable_by_user(current_user, current_user.teams)
    private_modules_number = assigned_modules.where.not(id: viewable_modules).count
    render json: {
      html: render_to_string(partial: 'shared/my_modules_list_partial', locals: {
                               my_modules: viewable_modules,
                               private_modules_number: private_modules_number
                             })
    }
  end

  def reminder_repository_cells
    render json: @repository_row.repository_cells.with_active_reminder(current_user)
  end

  def archive_records
    service = RepositoryActions::ArchiveRowsService.call(repository: @repository,
                                                         repository_rows: selected_rows_in_repo_params,
                                                         user: current_user,
                                                         team: current_team)

    if service.succeed?
      render json: { flash: t('repositories.archive_records.success_flash', repository: escape_input(@repository.name)) }
    else
      render json: { error: service.error_message }, status: :unprocessable_entity
    end
  end

  def restore_records
    service = RepositoryActions::RestoreRowsService.call(repository: @repository,
                                                         repository_rows: selected_rows_in_repo_params,
                                                         user: current_user,
                                                         team: current_team)

    if service.succeed?
      render json: { flash: t('repositories.restore_records.success_flash', repository: escape_input(@repository.name)) }
    else
      render json: { error: service.error_message }, status: :unprocessable_entity
    end
  end

  def active_reminder_repository_cells
    reminder_cells = @repository_row.repository_cells.with_active_reminder(current_user).distinct
    render json: {
      html: render_to_string(partial: 'shared/repository_row_reminder', formats: :html, locals: {
                               reminders: reminder_cells
                             })
    }
  end

  def actions_toolbar
    render json: {
      actions:
        Toolbars::RepositoryRowsService.new(
          current_user,
          repository_row_ids: params[:repository_row_ids].split(',')
        ).actions
    }
  end

  def relationships
    render json: {
      repository_row: RepositoryRowSerializer.new(@repository_row),
      parents: @repository_row.parent_repository_rows.map { |r| RepositoryRowSerializer.new(r) },
      children: @repository_row.child_repository_rows.map { |r| RepositoryRowSerializer.new(r) }
    }
  end

  private

  include StringUtility
  AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached)

  def load_repository
    @repository = Repository.readable_by_user(current_user)
                            .eager_load(:repository_columns)
                            .find_by(id: params[:repository_id])
    render_404 unless @repository
  end

  def load_repository_or_snapshot
    if params[:form_repository_rows_field_value_id]
      @form_repository_rows_field_value =
        FormRepositoryRowsFieldValue.find_by(id: params[:form_repository_rows_field_value_id])
    end

    @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) ||
                  RepositorySnapshot.find_by(id: params[:repository_id])

    render_404 unless @form_repository_rows_field_value || @repository
  end

  def load_repository_row
    @repository_row = if @form_repository_rows_field_value
                        @form_repository_rows_field_value.reified_repository_row_by_id(params[:id])
                      else
                        @repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
                      end

    render_404 unless @repository_row
  end

  def load_repository_rows
    @repository_rows = @repository.repository_rows.eager_load(:repository_columns).where(id: params[:row_ids])

    render_404 if @repository_rows.blank?
  end

  def check_read_permissions
    if @form_repository_rows_field_value
      render_403 unless can_read_my_module?(
        @form_repository_rows_field_value.form_response.step.protocol.my_module
      )
    else
      render_403 unless can_read_repository?(@repository)
    end
  end

  def check_snapshotting_status
    return if @repository.repository_snapshots.provisioning.none?

    render json: {
      flash: t('repositories.index.snapshot_provisioning_in_progress')
    }, status: :unprocessable_entity
  end

  def check_create_permissions
    render_403 unless can_create_repository_rows?(@repository)
  end

  def check_manage_permissions
    render_403 unless can_manage_repository_rows?(@repository)
  end

  def check_delete_permissions
    render_403 unless can_delete_repository_rows?(@repository)
  end

  def remove_file_columns_params
    JSON.parse(params.fetch(:remove_file_columns) { '[]' })
  end

  def selected_params
    params.permit(selected_rows: []).to_h[:selected_rows]
  end

  # Selected rows in scope of current @repository
  def selected_rows_in_repo_params
    process_ids = params[:selected_rows].map(&:to_i).uniq
    @repository.repository_rows.where(id: process_ids).pluck(:id)
  end

  def load_available_rows
    @repository.repository_rows
               .active
               .includes(:repository_cells)
               .name_like(search_params[:q])
               .limit(Constants::SEARCH_LIMIT)
               .select(:id, :name)
               .collect do |row|
                 with_asset_cell = row.repository_cells.where(
                   'repository_cells.repository_column_id = ?',
                   search_params[:repository_column_id]
                 )
                 AvailableRepositoryRow.new(row.id,
                                            ellipsize(row.name, 75, 50),
                                            with_asset_cell.present?)
               end
  end

  def search_params
    params.permit(:q, :repository_id, :repository_column_id)
  end

  def record_annotation_notification(record, cell, old_text = nil)
    smart_annotation_notification(
      old_text: old_text,
      new_text: cell.value.data,
      subject: cell.repository_row,
      title: t('notifications.repository_annotation_title',
               user: current_user.full_name,
               column: cell.repository_column.name,
               record: record.name,
               repository: record.repository.name),
      message: t('notifications.repository_annotation_message_html',
                 record: link_to(record.name, repository_url(@repository)),
                 column: link_to(cell.repository_column.name, repository_url(@repository)))
    )
  end

  def fetch_list_items(cell)
    return [] if cell.value_type != 'RepositoryListValue'
    RepositoryListItem.where(repository: @repository)
                      .where(repository_column: cell.repository_column)
                      .limit(Constants::SEARCH_LIMIT)
                      .pluck(:id, :data)
                      .map { |li| [li[0], escape_input(li[1])] }
  end

  def fetch_columns_list_items
    collection = []
    @repository.repository_columns
               .list_type
               .preload(:repository_list_items)
               .each do |column|
      collection << {
        column_id: column.id,
        list_items: column.repository_list_items
                          .limit(Constants::SEARCH_LIMIT)
                          .pluck(:id, :data)
                          .map { |li| [li[0], escape_input(li[1])] }
      }
    end
    collection
  end

  def update_params
    params.permit(:my_module_id, :is_output, repository_row: :name, repository_cells: {}).to_h
  end

  def log_activity(type_of, repository_row, message_items = {})
    Activities::CreateActivityService
      .call(activity_type: type_of,
            owner: current_user,
            subject: repository_row,
            team: @repository.team,
            message_items: message_items)
  end
end
