class CreateTagVersions < ActiveRecord::Migration[8.0]
  class Tag < ActiveRecord::Base
    self.table_name = 'tags'
  end

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

  class TagImplication < ActiveRecord::Base
    self.table_name = 'tag_implications'
  end

  class TagVersion < ActiveRecord::Base
    self.table_name = 'tag_versions'
  end

  class NicoTagVersion < ActiveRecord::Base
    self.table_name = 'nico_tag_versions'
  end

  class NicoTagRelation < ActiveRecord::Base
    self.table_name = 'nico_tag_relations'
  end

  def up
    create_table :tag_versions do |t|
      t.references :tag, null: false, foreign_key: true, index: false
      t.integer :version_no, null: false
      t.string :event_type, null: false
      t.string :name, null: false
      t.string :category, null: false
      t.text :aliases, null: false
      t.text :parent_tag_ids, null: false
      t.datetime :created_at, null: false
      t.references :created_by_user, foreign_key: { to_table: :users }, index: false

      t.index [:tag_id, :version_no], unique: true
      t.index :created_at
      t.index [:tag_id, :created_at], order: { created_at: :desc }
      t.index [:created_by_user_id, :created_at], order: { created_at: :desc }
      t.check_constraint 'version_no > 0',
                         name: 'tag_versions_version_no_positive'
    end

    create_table :nico_tag_versions do |t|
      t.references :tag, null: false, foreign_key: true, index: false
      t.integer :version_no, null: false
      t.string :event_type, null: false
      t.string :name, null: false
      t.text :linked_tags, null: false
      t.datetime :created_at, null: false
      t.references :created_by_user, foreign_key: { to_table: :users }, index: false

      t.index [:tag_id, :version_no], unique: true
      t.index :created_at
      t.index [:tag_id, :created_at], order: { created_at: :desc }
      t.index [:created_by_user_id, :created_at], order: { created_at: :desc }
      t.check_constraint 'version_no > 0',
                         name: 'nico_tag_versions_version_no_positive'
    end

    TagVersion.reset_column_information
    say_with_time 'Backfilling tag_versions' do
      Tag.where(discarded_at: nil)
         .where.not(category: 'nico')
         .find_in_batches(batch_size: 500) do |tags|
        tag_ids = tags.map(&:id)

        tag_implication_rows_by_tag_id =
            TagImplication
            .where(tag_id: tag_ids)
            .pluck(:tag_id, :parent_tag_id)
            .each_with_object(Hash.new { |h, k| h[k] = [] }) do |row, h|
              h[row[0]] << row[1]
            end

        tag_name_rows_by_tag_id =
            TagName
            .joins('INNER JOIN tags ON tags.tag_name_id = tag_names.id')
            .where(tags: { id: tag_ids })
            .pluck('tags.id', 'tag_names.name')
            .each_with_object({ }) do |row, h|
              h[row[0]] = row[1]
            end

        tag_alias_rows_by_tag_id =
            TagName
            .joins('INNER JOIN tags ON tags.tag_name_id = tag_names.canonical_id')
            .where(tags: { id: tag_ids })
            .where(tag_names: { discarded_at: nil })
            .pluck('tags.id', 'tag_names.name')
            .each_with_object(Hash.new { |h, k| h[k] = [] }) do |row, h|
              h[row[0]] << row[1]
            end

        TagVersion.insert_all(tags.map { |tag|
          { tag_id: tag.id,
            version_no: 1,
            event_type: 'create',
            name: tag_name_rows_by_tag_id[tag.id],
            category: tag.category,
            aliases: tag_alias_rows_by_tag_id[tag.id].sort.join(' '),
            parent_tag_ids: tag_implication_rows_by_tag_id[tag.id].sort.join(' '),
            created_at: tag.created_at,
            created_by_user_id: nil }
        })
      end
    end

    NicoTagVersion.reset_column_information
    say_with_time 'Backfilling nico_tag_versions' do
      Tag.where(discarded_at: nil, category: 'nico')
         .find_in_batches(batch_size: 500) do |tags|
        tag_ids = tags.map(&:id)

        tag_name_rows_by_tag_id =
            TagName
            .joins('INNER JOIN tags ON tags.tag_name_id = tag_names.id')
            .where(tags: { id: tag_ids })
            .pluck('tags.id', 'tag_names.name')
            .each_with_object({ }) do |row, h|
              h[row[0]] = row[1]
            end

        nico_tag_relation_rows_by_tag_id =
            NicoTagRelation
            .joins('INNER JOIN tags nico_tags ON nico_tags.id = nico_tag_relations.nico_tag_id')
            .joins('INNER JOIN tags linked_tags ON linked_tags.id = nico_tag_relations.tag_id')
            .joins('INNER JOIN tag_names ON tag_names.id = linked_tags.tag_name_id')
            .where(nico_tags: { id: tag_ids })
            .where(linked_tags: { discarded_at: nil })
            .where(tag_names: { discarded_at: nil })
            .pluck('nico_tags.id', 'tag_names.name')
            .each_with_object(Hash.new { |h, k| h[k] = [] }) do |row, h|
              h[row[0]] << row[1]
            end

        NicoTagVersion.insert_all(tags.map { |tag|
          { tag_id: tag.id,
            version_no: 1,
            event_type: 'create',
            name: tag_name_rows_by_tag_id[tag.id],
            linked_tags: nico_tag_relation_rows_by_tag_id[tag.id].sort.join(' '),
            created_at: tag.created_at,
            created_by_user_id: nil }
        })
      end
    end
  end

  def down
    drop_table :nico_tag_versions
    drop_table :tag_versions
  end
end
