# frozen_string_literal: true

class FormRepositoryRowsFieldValue < FormFieldValue
  RELATIONAL_VALUE_INCLUDES = {
    repository_list_value: {
      include: :repository_list_item
    },
    repository_asset_value: {
      include: { asset: { include: { file: { include: :blob } } } }
    },
    repository_status_value: {
      include: :repository_status_item
    },
    repository_checklist_value: {
      include: :repository_checklist_items
    }
  }.freeze

  has_many_attached :snapshot_files

  before_save :attach_snapshot_files

  def attach_snapshot_files
    if data.present?
      # find asset values and attach file blobs
      data.map { |r| r['repository_cells'] }.flatten.filter { |c| c['value_type'] == 'RepositoryAssetValue' }.each do |c|
        blob_id = c.dig('repository_asset_value', 'asset', 'file', 'blob', 'id')

        next if snapshot_files.find { |f| f.blob.id == blob_id }

        snapshot_files.attach(ActiveStorage::Blob.find(blob_id))
      end
    end
  end

  def value=(repository_row_ids)
    # removes repository row snapshots if they are not present in the new id array,
    # keeps already existing snapshots intact, and
    # adds new snapshots if not yet present
    self.data ||= []

    # If incoming data is empty, we need to nulify data
    if repository_row_ids.blank?
      self.data = nil
      return
    end

    removed_repository_row_ids = data.pluck('id') - repository_row_ids
    existing_repository_row_ids = data.pluck('id') & repository_row_ids

    self.data =
      data.filter { |r| removed_repository_row_ids.exclude?(r['id']) } +
      repository_row_snapshots(repository_row_ids - existing_repository_row_ids)
  end

  def value
    data
  end

  def reified_repository_row_by_id(repository_row_id)
    # build an in-memory, read-only representation of the repository row in the snapshot

    row_attributes = data.find { |r| r['id'] == repository_row_id.to_i }
    row = RepositoryRow.new(row_attributes.except('repository_cells'))

    row.repository_cells = row_attributes['repository_cells'].map do |cell_attributes|
      reified_repository_cell(row, cell_attributes)
    end

    row.snapshot_by_name = User.find_by(id: row.snapshot_by_id)&.full_name
    row.readonly!
    row
  end

  def formatted
    value&.map { |i| "#{i['name']} (#{RepositoryRow::ID_PREFIX}#{i['id']})" }&.join(' | ')
  end

  private

  def repository_row_snapshots(repository_row_ids)
    RepositoryRow.includes(repository_cells: %i(repository_column value)).where(id: repository_row_ids).map do |repository_row|
      repository_row.as_json(include: { repository_cells: { include: [:repository_column, :value, RELATIONAL_VALUE_INCLUDES] } }).merge(
        {
          'snapshot_at' => DateTime.current,
          'snapshot_by_id' => created_by_id
        }
      )
    end
  end

  def reified_repository_cell(repository_row, cell_attributes)
    repository_cell = repository_row.repository_cells.build(
      cell_attributes.except('value', 'repository_column', *RELATIONAL_VALUE_INCLUDES.keys.map(&:to_s))
    )

    repository_column = RepositoryColumn.new(cell_attributes['repository_column'])
    repository_column.readonly!

    repository_cell.assign_attributes(
      repository_column_id: cell_attributes['repository_column']['id'],
      repository_row_id: repository_row.id,
      repository_column: repository_column,
      value: reified_repository_cell_value(repository_row, cell_attributes)
    )

    repository_cell.readonly!
    repository_cell
  end

  def reified_repository_cell_value(repository_row, cell_attributes)
    cell_value = cell_attributes['repository_column']['data_type'].constantize.new(
      cell_attributes['value'].except('repository_column')
    )

    # mock relational values in-memory
    case cell_value
    when RepositoryAssetValue
      snapshot_file = snapshot_files.find do |f|
        f.blob.id == cell_attributes.dig('repository_asset_value', 'asset', 'file', 'blob', 'id')
      end

      cell_value.asset = Asset.new(
        cell_attributes['repository_asset_value']['asset'].except('file').merge(snapshot: true)
      )

      cell_value.asset.file = snapshot_file.blob
      cell_value.asset.readonly!
    when RepositoryListValue
      list_item = RepositoryListItem.new(cell_attributes.dig('repository_list_value', 'repository_list_item'))
      cell_value.repository_list_item = list_item
      list_item.readonly!
      list_item
    when RepositoryStatusValue
      status_item = RepositoryStatusItem.new(cell_attributes.dig('repository_status_value', 'repository_status_item'))
      cell_value.repository_status_item = status_item
      status_item.readonly!
      status_item
    when RepositoryChecklistValue
      cell_value.define_singleton_method(:repository_checklist_items) do
        cell_attributes.dig('repository_checklist_value', 'repository_checklist_items').map do |ci|
          checklist_item = RepositoryChecklistItem.new(ci)
          checklist_item.readonly!
          checklist_item
        end
      end
    when RepositoryStockValue
      # override has_one through association for repository_row to read from memory
      cell_value.define_singleton_method(:repository_row) do
        repository_row
      end
    end

    cell_value.readonly!
    cell_value
  end
end
