require 'json'

class BackfillInitialMaterialVersions < ActiveRecord::Migration[8.0]
  class MigrationMaterial < ActiveRecord::Base
    self.table_name = 'materials'
  end

  class MigrationMaterialExportItem < ActiveRecord::Base
    self.table_name = 'material_export_items'
  end

  class MigrationMaterialVersion < ActiveRecord::Base
    self.table_name = 'material_versions'
  end

  class MigrationStorageAttachment < ActiveRecord::Base
    self.table_name = 'active_storage_attachments'
  end

  class MigrationStorageBlob < ActiveRecord::Base
    self.table_name = 'active_storage_blobs'
  end

  class MigrationTag < ActiveRecord::Base
    self.table_name = 'tags'
  end

  class MigrationTagName < ActiveRecord::Base
    self.table_name = 'tag_names'
  end

  def up
    say_with_time 'Backfilling initial material versions' do
      materials_without_versions.find_in_batches(batch_size: 500) do |materials|
        material_ids = materials.map(&:id)
        tags_by_id = load_tags(materials.map(&:tag_id).compact.uniq)
        export_paths_by_material_id = load_export_paths(material_ids)
        file_snapshots_by_material_id = load_file_snapshots(material_ids)

        rows = materials.map do |material|
          tag = tags_by_id[material.tag_id]
          file_snapshot = file_snapshots_by_material_id[material.id] || empty_file_snapshot

          { material_id: material.id,
            version_no: 1,
            event_type: 'create',
            url: material.url,
            parent_id: material.parent_id,
            tag_id: material.tag_id,
            tag_name: tag&.fetch(:name, nil),
            tag_category: tag&.fetch(:category, nil),
            source_kind: material.source_kind,
            source_uri: material.source_uri,
            source_path: material.source_path,
            source_file_id: material.source_file_id,
            normalized_source_key: material.normalized_source_key,
            export_paths_json: export_paths_by_material_id[material.id] || { },
            discarded_at: material.discarded_at,
            file_blob_id: file_snapshot[:file_blob_id],
            file_filename: file_snapshot[:file_filename],
            file_content_type: file_snapshot[:file_content_type],
            file_byte_size: file_snapshot[:file_byte_size],
            file_checksum: file_snapshot[:file_checksum],
            file_sha256: file_snapshot[:file_sha256],
            created_by_user_id: material.created_by_user_id,
            updated_by_user_id: material.updated_by_user_id,
            created_at: material.created_at,
            updated_at: material.created_at }
        end

        MigrationMaterialVersion.insert_all!(rows) if rows.present?
      end
    end

    execute <<~SQL.squish
      UPDATE materials
      SET version_no = COALESCE(
        (
          SELECT MAX(material_versions.version_no)
          FROM material_versions
          WHERE material_versions.material_id = materials.id
        ),
        1
      )
    SQL
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end

  private

  def materials_without_versions
    MigrationMaterial.where.not(id: MigrationMaterialVersion.select(:material_id))
  end

  def load_tags tag_ids
    return { } if tag_ids.empty?

    tag_name_ids_by_tag_id = MigrationTag.where(id: tag_ids).pluck(:id, :tag_name_id).to_h
    tag_names_by_id =
        MigrationTagName
        .where(id: tag_name_ids_by_tag_id.values.uniq)
        .pluck(:id, :name)
        .to_h
    categories_by_tag_id = MigrationTag.where(id: tag_ids).pluck(:id, :category).to_h

    tag_ids.each_with_object({ }) do |tag_id, hash|
      hash[tag_id] = { name: tag_names_by_id[tag_name_ids_by_tag_id[tag_id]],
                       category: categories_by_tag_id[tag_id] }
    end
  end

  def load_export_paths material_ids
    MigrationMaterialExportItem
      .where(material_id: material_ids)
      .order(:material_id, :profile)
      .pluck(:material_id, :profile, :export_path)
      .each_with_object({ }) do |(material_id, profile, export_path), hash|
        hash[material_id] ||= { }
        hash[material_id][profile] = export_path
      end
  end

  def load_file_snapshots material_ids
    attachments_by_material_id =
        MigrationStorageAttachment
        .where(record_type: 'Material', name: 'file', record_id: material_ids)
        .pluck(:record_id, :blob_id)
        .to_h

    blobs_by_id =
        MigrationStorageBlob
        .where(id: attachments_by_material_id.values.uniq)
        .index_by(&:id)

    attachments_by_material_id.each_with_object({ }) do |(material_id, blob_id), hash|
      blob = blobs_by_id[blob_id]
      next unless blob

      hash[material_id] = {
        file_blob_id: blob.id,
        file_filename: blob.filename.to_s,
        file_content_type: blob.content_type,
        file_byte_size: blob.byte_size,
        file_checksum: blob.checksum,
        file_sha256: blob_metadata(blob)['sha256']
      }
    end
  end

  def empty_file_snapshot
    { file_blob_id: nil,
      file_filename: nil,
      file_content_type: nil,
      file_byte_size: nil,
      file_checksum: nil,
      file_sha256: nil }
  end

  def blob_metadata blob
    metadata = blob.metadata
    return metadata if metadata.is_a?(Hash)
    return { } if metadata.blank?

    JSON.parse(metadata)
  rescue JSON::ParserError
    { }
  end
end
