require 'rails_helper'

RSpec.describe PostImportPreviewer do
  before do
    allow(Preview::UrlSafety).to receive(:validate) do |url|
      [URI.parse(url), ['8.8.8.8']]
    end
  end

  def row(source_row:, url:, attributes: { }, provenance: { }, **values)
    {
      source_row:,
      url:,
      attributes:,
      provenance:
    }.merge(values)
  end

  describe '#preview_rows' do
    it 'marks an existing post for skipping without fetching metadata' do
      existing = create(:post, url: 'https://example.com/existing')
      expect(PostMetadataFetcher).not_to receive(:fetch)

      result = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://EXAMPLE.com/existing/')
      ]).first

      expect(result).to include(
        url: 'https://example.com/existing',
        skip_reason: 'existing',
        existing_post_id: existing.id,
        validation_errors: {}
      )
      expect(result.fetch(:field_warnings)).to eq({})
    end

    it 'reports list duplicates as URL errors instead of existing skips' do
      create(:post, url: 'https://example.com/duplicate')
      expect(PostMetadataFetcher).not_to receive(:fetch)

      results = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://example.com/duplicate'),
        row(source_row: 2, url: 'https://EXAMPLE.com/duplicate/')
      ])

      expect(results.map { _1[:validation_errors] }).to all(
        include(url: ['URL が重複しています．'])
      )
      expect(results).to all(include(skip_reason: nil, existing_post_id: nil))
    end

    it 'does not fetch metadata for an unsafe URL' do
      allow(Preview::UrlSafety).to receive(:validate)
          .and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません．')
      expect(PostMetadataFetcher).not_to receive(:fetch)

      result = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://unsafe.example/post')
      ]).first

      expect(result.fetch(:validation_errors)).to include(
        url: ['安全でない接続先は使用できません．']
      )
    end

    it 'applies metadata to automatic fields and recognises metadata tags' do
      Tag.create!(name: 'known-tag', category: :general)
      allow(PostMetadataFetcher).to receive(:fetch).and_return(
        title: 'metadata title',
        thumbnail_base: 'https://example.com/thumb.jpg',
        duration: '2',
        tags: 'known-tag'
      )

      result = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://example.com/new')
      ]).first

      expect(result.fetch(:attributes)).to include(
        'title' => 'metadata title',
        'thumbnail_base' => 'https://example.com/thumb.jpg',
        'duration' => '2',
        'tags' => 'known-tag'
      )
      expect(result.fetch(:field_warnings)).not_to have_key('tags')
      expect(PostMetadataFetcher).to have_received(:fetch).once
    end

    it 'preserves manual empty values when metadata is available' do
      allow(PostMetadataFetcher).to receive(:fetch).and_return(
        title: 'metadata title',
        tags: 'metadata-tag'
      )

      result = described_class.new.preview_rows(rows: [
        row(
          source_row: 1,
          url: 'https://example.com/manual',
          attributes: { title: '', tags: '' },
          provenance: { title: 'manual', tags: 'manual' },
          tag_sources: { automatic: 'old-tag', manual: '' }
        )
      ]).first

      expect(result.fetch(:attributes)).to include('title' => '', 'tags' => '')
      expect(result.fetch(:tag_sources)).to include('manual' => '')
    end

    it 'clears warnings derived from an old metadata URL' do
      allow(PostMetadataFetcher).to receive(:fetch).and_return(
        title: 'new title',
        thumbnail_base: 'https://example.com/new-thumb.jpg',
        tags: ''
      )

      result = described_class.new.preview_rows(rows: [
        row(
          source_row: 1,
          url: 'https://example.com/new-url',
          attributes: { title: 'old title', tags: 'old-tag' },
          provenance: { title: 'automatic', tags: 'automatic' },
          tag_sources: { automatic: 'old-tag', manual: '' },
          metadata_url: 'https://example.com/old-url',
          field_warnings: { tags: ['old tag warning'], url: ['old fetch warning'] },
          base_warnings: ['old base warning']
        )
      ]).first

      expect(result.fetch(:attributes)).to include('title' => 'new title', 'tags' => '')
      expect(result.fetch(:base_warnings)).to eq([])
      expect(result.fetch(:field_warnings).values.flatten)
          .not_to include('old tag warning', 'old fetch warning')
    end

    it 'isolates an unexpected metadata failure to the affected URL' do
      allow(PostMetadataFetcher).to receive(:fetch) do |url|
        raise 'fetch failure' if url.include?('failure')

        { title: 'successful title', tags: '' }
      end
      allow(Rails.logger).to receive(:error)

      results = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://example.com/failure'),
        row(source_row: 2, url: 'https://example.com/success')
      ])

      expect(results[0].fetch(:field_warnings)).to include(
        'url' => [described_class::METADATA_FETCH_WARNING]
      )
      expect(results[1].fetch(:attributes)).to include('title' => 'successful title')
    end

    it 'turns an unsafe URL detected during metadata fetching into a URL error' do
      allow(PostMetadataFetcher).to receive(:fetch)
          .and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先です．')
      allow(Rails.logger).to receive(:info)

      result = described_class.new.preview_rows(rows: [
        row(source_row: 1, url: 'https://example.com/redirects-to-private')
      ]).first

      expect(result.fetch(:validation_errors)).to include(
        url: ['安全でない接続先です．']
      )
    end

    it 'validates parent IDs from the preloaded set' do
      parent = create(:post)

      results = described_class.new.preview_rows(
        rows: [
          row(source_row: 1, url: 'https://example.com/valid-parent',
              attributes: { parent_post_ids: parent.id.to_s }),
          row(source_row: 2, url: 'https://example.com/missing-parent',
              attributes: { parent_post_ids: '999999' })
        ],
        fetch_metadata: false
      )

      expect(results[0].fetch(:validation_errors)).not_to have_key(:parent_post_ids)
      expect(results[1].fetch(:validation_errors)).to include(
        parent_post_ids: ['存在しない親投稿 Id. があります．']
      )
    end
  end
end
