require 'rails_helper'

RSpec.describe PostImportRunner do
  let(:actor) { create(:user, :member) }

  def row(source_row: 1, url: 'https://example.com/post')
    {
      sourceRow: source_row,
      url:,
      attributes: { title: 'title', tags: '' },
      provenance: { url: 'manual', title: 'manual', tags: 'manual' },
      tagSources: { automatic: '', manual: '' }
    }
  end

  def preview(source_row: 1, errors: { }, skip_reason: nil, existing_post_id: nil)
    {
      source_row:,
      attributes: { 'title' => 'title', 'tags' => '' },
      validation_errors: errors,
      skip_reason:,
      existing_post_id:
    }
  end

  it 'previews the whole batch once before processing individual rows' do
    rows = [row, row(source_row: 2, url: 'https://example.com/two')]
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    expect(previewer).to receive(:preview_rows)
        .with(
          rows: satisfy { _1.map { |row_value| row_value['source_row'] } == [1, 2] },
          fetch_metadata: false
        )
        .and_return([preview, preview(source_row: 2)])
    allow(PostCreator).to receive(:new).and_return(
      instance_double(PostCreator, create!: create(:post))
    )

    result = described_class.new(actor:, rows:).run

    expect(result).to include(created: 2, skipped: 0, failed: 0)
  end

  it 'treats validation errors as failures before an existing skip' do
    existing = create(:post)
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([
      preview(errors: { url: ['URL が重複しています．'] },
              skip_reason: 'existing', existing_post_id: existing.id)
    ])
    expect(PostCreator).not_to receive(:new)

    result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first

    expect(result).to include(
      status: 'failed',
      errors: { url: ['URL が重複しています．'] },
      recoverable: true
    )
  end

  it 'returns the existing post ID for skipped rows' do
    existing = create(:post)
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([
      preview(skip_reason: 'existing', existing_post_id: existing.id)
    ])

    result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first

    expect(result).to eq(
      source_row: 1,
      status: 'skipped',
      existing_post_id: existing.id
    )
  end

  it 'converts a URL uniqueness validation race into a skip' do
    existing = create(:post, url: 'https://example.com/race')
    invalid = Post.new(url: existing.url)
    invalid.errors.add(:url, :taken)
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([preview])
    creator = instance_double(PostCreator)
    allow(PostCreator).to receive(:new).and_return(creator)
    allow(creator).to receive(:create!).and_raise(ActiveRecord::RecordInvalid.new(invalid))

    result = described_class.new(
      actor:,
      rows: [row(url: 'https://EXAMPLE.com/race/')]
    ).run.fetch(:rows).first

    expect(result).to include(status: 'skipped', existing_post_id: existing.id)
  end

  it 're-raises RecordNotUnique errors unrelated to the posts URL index' do
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([preview])
    creator = instance_double(PostCreator)
    allow(PostCreator).to receive(:new).and_return(creator)
    allow(creator).to receive(:create!)
        .and_raise(ActiveRecord::RecordNotUnique, 'other_unique_index')

    expect {
      described_class.new(actor:, rows: [row]).run
    }.to raise_error(ActiveRecord::RecordNotUnique)
  end

  it 'converts a posts URL index race into a skip' do
    existing = create(:post, url: 'https://example.com/index-race')
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([preview])
    creator = instance_double(PostCreator)
    allow(PostCreator).to receive(:new).and_return(creator)
    allow(creator).to receive(:create!).and_raise(
      ActiveRecord::RecordNotUnique,
      'duplicate key index_posts_on_url'
    )

    result = described_class.new(
      actor:,
      rows: [row(url: 'https://EXAMPLE.com/index-race/')]
    ).run.fetch(:rows).first

    expect(result).to include(status: 'skipped', existing_post_id: existing.id)
  end

  it 'returns thumbnail warnings from PostCreator on a created row' do
    previewer = instance_double(PostImportPreviewer)
    allow(PostImportPreviewer).to receive(:new).and_return(previewer)
    allow(previewer).to receive(:preview_rows).and_return([preview])
    created_post = create(:post)
    creator = instance_double(
        PostCreator,
        create!: created_post,
        field_warnings: { thumbnail_base: ['サムネール画像を取得できませんでした．'] })
    allow(PostCreator).to receive(:new).and_return(creator)

    result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first

    expect(result).to include(
      status: 'created',
      field_warnings: { thumbnail_base: ['サムネール画像を取得できませんでした．'] }
    )
  end
end
