require 'rails_helper'

RSpec.describe 'Post imports API', type: :request do
  let(:member) { create(:user, :member) }

  before do
    allow(Preview::UrlSafety).to receive(:validate) do |url|
      [URI.parse(url), ['8.8.8.8']]
    end
    allow(PostMetadataFetcher).to receive(:fetch).and_return(
      title: 'fetched title',
      thumbnail_base: nil,
      tags: ''
    )
  end

  describe 'POST /posts/import/preview' do
    it 'requires a member' do
      sign_out
      post '/posts/import/preview', params: { source: 'https://example.com/post' }
      expect(response).to have_http_status(:unauthorized)

      sign_in_as(create(:user, :guest))
      post '/posts/import/preview', params: { source: 'https://example.com/post' }
      expect(response).to have_http_status(:forbidden)
    end

    it 'parses a URL list and returns preview rows' do
      sign_in_as(member)

      post '/posts/import/preview', params: {
        source: " https://example.com/one \r\n\r\nhttps://example.com/two"
      }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('rows').map { _1.fetch('source_row') }).to eq([1, 3])
      expect(json.fetch('rows').map { _1.fetch('url') }).to eq([
        'https://example.com/one',
        'https://example.com/two'
      ])
    end

    it 'returns a safe 400 response for an invalid source' do
      sign_in_as(member)

      post '/posts/import/preview', params: { source: '' }

      expect(response).to have_http_status(:bad_request)
      expect(json.fetch('message')).to eq('URL を入力してください．')
    end
  end

  describe 'POST /posts/import/validate' do
    it 'accepts camel-case row properties and returns their warnings' do
      sign_in_as(member)

      post '/posts/import/validate', params: {
        rows: [{
          sourceRow: '1',
          url: 'https://example.com/post',
          metadataUrl: 'https://example.com/post',
          attributes: { title: 'manual title' },
          provenance: { url: 'manual', title: 'manual' },
          tagSources: { automatic: '', manual: '' },
          fieldWarnings: { title: ['old warning'] },
          baseWarnings: ['base warning']
        }],
        changed_row: -1
      }

      expect(response).to have_http_status(:ok)
      result = json.fetch('rows').first
      expect(result.fetch('source_row')).to eq(1)
      expect(result.fetch('tag_sources')).to eq('automatic' => '', 'manual' => '')
      expect(result.fetch('field_warnings')).to eq('title' => ['old warning'])
      expect(result.fetch('base_warnings')).to eq(['base warning'])
    end

    it 'rejects a non-array rows value with 400' do
      sign_in_as(member)

      post '/posts/import/validate', params: { rows: { sourceRow: 1 }, changed_row: -1 }

      expect(response).to have_http_status(:bad_request)
      expect(json.fetch('message')).to eq('取込行の形式が不正です．')
    end

    it 'returns original created datetime validation errors for minute precision' do
      sign_in_as(member)

      post '/posts/import/validate', params: {
        rows: [{
          sourceRow: 1,
          url: 'https://example.com/post',
          metadataUrl: 'https://example.com/post',
          attributes: {
            originalCreatedFrom: '2020-01-01T00:00:30Z',
            originalCreatedBefore: '2020-01-01T00:01Z' },
          provenance: {
            url: 'manual',
            originalCreatedFrom: 'manual',
            originalCreatedBefore: 'manual' },
          tagSources: { automatic: '', manual: '' }
        }],
        changed_row: -1
      }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('rows').first.fetch('validation_errors')).to include(
        'original_created_from' => ['オリジナルの作成日時は分単位で入力してください．']
      )
      expect(json.fetch('rows').first.fetch('validation_errors'))
          .not_to have_key('original_created_at')
    end
  end

  describe 'POST /posts/import' do
    it 'keeps the duration string contract through preview, validate, and import' do
      sign_in_as(member)
      allow(PostMetadataFetcher).to receive(:fetch).and_return(
        title: 'fetched title',
        thumbnail_base: nil,
        duration: '2.5',
        tags: '動画'
      )

      post '/posts/import/preview', params: {
        source: 'https://example.com/video'
      }
      preview_row = json.fetch('rows').first
      expect(preview_row.dig('attributes', 'duration')).to eq('2.5')

      post '/posts/import/validate', params: {
        rows: [{
          sourceRow: preview_row.fetch('source_row'),
          url: preview_row.fetch('url'),
          attributes: preview_row.fetch('attributes'),
          provenance: preview_row.fetch('provenance'),
          tagSources: preview_row.fetch('tag_sources'),
          metadataUrl: preview_row.fetch('metadata_url')
        }],
        changed_row: -1
      }
      validated_row = json.fetch('rows').first
      expect(validated_row.dig('attributes', 'duration')).to eq('2.5')

      post '/posts/import', params: {
        rows: [{
          sourceRow: validated_row.fetch('source_row'),
          url: validated_row.fetch('url'),
          attributes: validated_row.fetch('attributes'),
          provenance: validated_row.fetch('provenance'),
          tagSources: validated_row.fetch('tag_sources'),
          metadataUrl: validated_row.fetch('metadata_url')
        }]
      }

      expect(response).to have_http_status(:ok)
      expect(Post.order(:id).last.video_ms).to eq(2_500)
    end

    it 'returns a formal skipped result for an existing post' do
      existing = create(:post, url: 'https://example.com/existing')
      sign_in_as(member)

      post '/posts/import', params: {
        rows: [{
          sourceRow: 1,
          url: existing.url,
          attributes: { title: 'ignored' },
          provenance: { url: 'manual', title: 'manual' },
          tagSources: { automatic: '', manual: '' }
        }]
      }

      expect(response).to have_http_status(:ok)
      expect(json).to include('created' => 0, 'skipped' => 1, 'failed' => 0)
      expect(json.fetch('rows').first).to include(
        'status' => 'skipped',
        'existing_post_id' => existing.id
      )
    end
  end
end
