require 'rails_helper'

RSpec.describe Youtube::Sync do
  let(:client) { instance_double(Youtube::ApiClient) }
  let(:sync) { described_class.new(client:) }

  before do
    allow(PostVersionRecorder).to receive(:record!)
    allow(PostVersionRecorder).to receive(:ensure_snapshot!)
    allow(sync).to receive(:attach_thumbnail_if_needed!)
  end

  describe '#attach_thumbnail_if_needed!' do
    it 'uses the common remote thumbnail attach path' do
      post = create(:post, thumbnail_base: nil)
      allow(sync).to receive(:attach_thumbnail_if_needed!).and_call_original

      expect(post).to receive(:attach_thumbnail_from_url!)
          .with('https://example.com/thumb.jpg')

      sync.send(
          :attach_thumbnail_if_needed!,
          post,
          'https://example.com/thumb.jpg')
    end

    it 'retries on a later sync when the previous remote attach failed' do
      post = create(:post, thumbnail_base: nil)
      allow(sync).to receive(:attach_thumbnail_if_needed!).and_call_original
      expect(post).to receive(:attach_thumbnail_from_url!)
          .with('https://example.com/thumb.jpg')
          .twice
          .and_raise(Post::RemoteThumbnailFetchFailed, 'failed')

      2.times do
        sync.send(
            :attach_thumbnail_if_needed!,
            post,
            'https://example.com/thumb.jpg')
      end
    end
  end

  describe '#sync!' do
    it 'returns without fetching video details when no video ids are discovered' do
      allow(sync).to receive(:query_terms).and_return([])
      allow(sync).to receive(:playlist_ids).and_return([])

      expect(client).not_to receive(:videos)

      sync.sync!
    end

    it 'discovers ids from search and all playlist pages' do
      allow(sync).to receive(:query_terms).and_return(['ぼざろクリーチャー'])
      allow(sync).to receive(:playlist_ids).and_return(['PL123'])
      allow(sync).to receive(:sync_since).and_return(Time.zone.parse('2026-05-01 00:00:00'))

      allow(client).to receive(:search_videos).with(
        q: 'ぼざろクリーチャー',
        published_after: Time.zone.parse('2026-05-01 00:00:00')
      ).and_return({
        'items' => [
          {
            'id' => {
              'videoId' => 'search-video-1'
            }
          }
        ]
      })

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: nil
      ).and_return({
        'items' => [
          {
            'contentDetails' => {
              'videoId' => 'playlist-video-1'
            }
          }
        ],
        'nextPageToken' => 'NEXT'
      })

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: 'NEXT'
      ).and_return({
        'items' => [
          {
            'snippet' => {
              'resourceId' => {
                'videoId' => 'playlist-video-2'
              }
            }
          }
        ]
      })

      expect(client).to receive(:videos).with(
        satisfy do |ids|
          ids.sort == ['playlist-video-1', 'playlist-video-2', 'search-video-1']
        end
      ).and_return({ 'items' => [] })

      sync.sync!
    end

    it 'creates a YouTube post with default tags when no deerjikist mapping exists' do
      Tag.tagme
      Tag.bot
      Tag.youtube
      Tag.video
      Tag.no_deerjikist

      allow(sync).to receive(:query_terms).and_return([])
      allow(sync).to receive(:playlist_ids).and_return(['PL123'])

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: nil
      ).and_return({
        'items' => [
          {
            'contentDetails' => {
              'videoId' => 'video-1'
            }
          }
        ]
      })

      allow(client).to receive(:videos).with(['video-1']).and_return({
        'items' => [
          youtube_video_item(
            id: 'video-1',
            title: 'YouTube テスト動画',
            channel_id: 'UC_NO_MAPPING'
          )
        ]
      })

      expect do
        sync.sync!
      end.to change(Post, :count).by(1)

      post = Post.find_by!(url: 'https://www.youtube.com/watch?v=video-1')
      tag_ids = post.tags.pluck(:id)

      expect(post.title).to eq('YouTube テスト動画')
      expect(post.uploaded_user_id).to be_nil
      expect(post.original_created_from).to eq(Time.zone.parse('2026-05-01 12:34:00'))
      expect(post.original_created_before).to eq(Time.zone.parse('2026-05-01 12:35:00'))

      expect(tag_ids).to include(Tag.tagme.id)
      expect(tag_ids).to include(Tag.bot.id)
      expect(tag_ids).to include(Tag.youtube.id)
      expect(tag_ids).to include(Tag.video.id)
      expect(tag_ids).to include(Tag.no_deerjikist.id)

      expect(PostVersionRecorder).to have_received(:record!).with(
        post:,
        event_type: :create,
        created_by_user: nil
      )
    end

    it 'uses deerjikist tag when channel id is mapped' do
      Tag.tagme
      Tag.bot
      Tag.youtube
      Tag.video
      Tag.no_deerjikist

      deerjikist_tag = Tag.find_or_create_by_tag_name!('テスト投稿者', category: :deerjikist)
      Deerjikist.create!(
        platform: 'youtube',
        code: 'UC_MAPPED',
        tag: deerjikist_tag
      )

      allow(sync).to receive(:query_terms).and_return([])
      allow(sync).to receive(:playlist_ids).and_return(['PL123'])

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: nil
      ).and_return({
        'items' => [
          {
            'contentDetails' => {
              'videoId' => 'video-1'
            }
          }
        ]
      })

      allow(client).to receive(:videos).with(['video-1']).and_return({
        'items' => [
          youtube_video_item(
            id: 'video-1',
            title: 'YouTube テスト動画',
            channel_id: 'UC_MAPPED'
          )
        ]
      })

      sync.sync!

      post = Post.find_by!(url: 'https://www.youtube.com/watch?v=video-1')
      tag_ids = post.tags.pluck(:id)

      expect(tag_ids).to include(deerjikist_tag.id)
      expect(tag_ids).not_to include(Tag.no_deerjikist.id)
    end

    it 'removes no_deerjikist when deerjikist mapping is added later' do
      Tag.no_deerjikist

      post = Post.create!(
        title: '旧タイトル',
        url: 'https://www.youtube.com/watch?v=video-1',
        uploaded_user_id: nil,
        original_created_from: Time.zone.parse('2026-05-01 00:00:00'),
        original_created_before: Time.zone.parse('2026-05-01 00:01:00')
      )
      PostTag.create!(post:, tag: Tag.no_deerjikist)

      deerjikist_tag = Tag.find_or_create_by_tag_name!('後から判明した投稿者', category: :deerjikist)
      Deerjikist.create!(
        platform: 'youtube',
        code: 'UC_MAPPED_LATER',
        tag: deerjikist_tag
      )

      allow(sync).to receive(:query_terms).and_return([])
      allow(sync).to receive(:playlist_ids).and_return(['PL123'])

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: nil
      ).and_return({
        'items' => [
          {
            'contentDetails' => {
              'videoId' => 'video-1'
            }
          }
        ]
      })

      allow(client).to receive(:videos).with(['video-1']).and_return({
        'items' => [
          youtube_video_item(
            id: 'video-1',
            title: '新タイトル',
            channel_id: 'UC_MAPPED_LATER'
          )
        ]
      })

      sync.sync!

      post.reload
      tag_ids = post.tags.pluck(:id)

      expect(post.title).to eq('新タイトル')
      expect(tag_ids).to include(deerjikist_tag.id)
      expect(tag_ids).not_to include(Tag.no_deerjikist.id)

      expect(PostVersionRecorder).to have_received(:ensure_snapshot!).with(
        post,
        created_by_user: nil
      )
      expect(PostVersionRecorder).to have_received(:record!).with(
        post:,
        event_type: :update,
        created_by_user: nil
      )
    end

    it 'matches existing youtu.be URL and does not create duplicate post' do
      post = Post.create!(
        title: '旧タイトル',
        url: 'https://youtu.be/video-1',
        uploaded_user_id: nil,
        original_created_from: Time.zone.parse('2026-05-01 00:00:00'),
        original_created_before: Time.zone.parse('2026-05-01 00:01:00')
      )

      allow(sync).to receive(:query_terms).and_return([])
      allow(sync).to receive(:playlist_ids).and_return(['PL123'])

      allow(client).to receive(:playlist_items).with(
        playlist_id: 'PL123',
        page_token: nil
      ).and_return({
        'items' => [
          {
            'contentDetails' => {
              'videoId' => 'video-1'
            }
          }
        ]
      })

      allow(client).to receive(:videos).with(['video-1']).and_return({
        'items' => [
          youtube_video_item(
            id: 'video-1',
            title: '新タイトル',
            channel_id: 'UC_NO_MAPPING'
          )
        ]
      })

      expect do
        sync.sync!
      end.not_to change(Post, :count)

      expect(post.reload.title).to eq('新タイトル')
    end
  end

  def youtube_video_item(id:, title:, channel_id:)
    {
      'id' => id,
      'snippet' => {
        'title' => title,
        'channelId' => channel_id,
        'publishedAt' => '2026-05-01T12:34:56Z',
        'thumbnails' => {
          'high' => {
            'url' => "https://img.youtube.com/#{id}.jpg"
          }
        },
        'tags' => ['tag-a', 'tag-b']
      }
    }
  end
end
