require 'rails_helper'


RSpec.describe 'nico:sync' do
  def stub_python(json_array)
    status = instance_double(Process::Status, success?: true)
    allow(Open3).to receive(:capture3).and_return([json_array.to_json, '', status])
  end

  def create_tag!(name, category:)
    tn = TagName.find_undiscard_or_create_by!(name: name.to_s.strip)
    Tag.find_undiscard_or_create_by!(tag_name_id: tn.id) { |t| t.category = category }
  end

  def link_nico_to_tag!(nico_tag, tag)
    NicoTagRelation.create!(nico_tag_id: nico_tag.id, tag_id: tag.id)
  end

  it '既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける' do
    # 既存 post（正規表現で拾われるURL）
    post = Post.create!(
      title: 'old',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil
    )

    # 既存の非nicoタグ（kept_non_nico_ids）
    kept_general = create_tag!('spec_kept', category: 'general')
    PostTag.create!(post: post, tag: kept_general)

    # 追加される linked tag を準備（nico tag に紐付く一般タグ）
    linked = create_tag!('spec_linked', category: 'general')
    nico = create_tag!('nico:AAA', category: 'nico')
    link_nico_to_tag!(nico, linked)

    # bot / tagme は task 内で使うので作っておく（Tag.bot/tagme がある前提）
    Tag.bot
    Tag.tagme

    # pythonの出力（AAA が付く）
    stub_python([{
        'code' => 'sm9',
        'title' => 't',
        'tags' => ['AAA'],
        'uploaded_at' => '2026-01-01 12:34:56',
        'deleted_at' => '2026-01-31 00:00:00' }])

    # 外部HTTPは今回「既存 post なので呼ばれない」はずだが、念のため塞ぐ
    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    run_rake_task('nico:sync')

    post.reload
    active_tag_names = post.tags.joins(:tag_name).pluck('tag_names.name')

    expect(active_tag_names).to include('spec_kept')
    expect(active_tag_names).to include('nico:AAA')
    expect(active_tag_names).to include('spec_linked')

    expect(post.original_created_from).to eq(Time.iso8601('2026-01-01T03:34:00Z'))
    expect(post.original_created_before).to eq(Time.iso8601('2026-01-01T03:35:00Z'))

    # 差分が出るので bot が付く（kept_non_nico_ids != desired_non_nico_ids）
    expect(active_tag_names).to include('bot操作')
  end

  it '既存 post のサムネール取得に共通 attach 経路を使ふ' do
    post = Post.create!(
        title: 'old',
        url: 'https://www.nicovideo.jp/watch/sm9',
        uploaded_user: nil)
    Tag.bot
    Tag.tagme

    stub_python([{ 'code' => 'sm9', 'title' => 't', 'tags' => [] }])
    allow(URI).to receive(:open) do
      StringIO.new(
          '<meta name="thumbnail" content="https://example.com/thumb.jpg">')
    end
    expect_any_instance_of(Post).to receive(:attach_thumbnail_from_url!)
        .with('https://example.com/thumb.jpg')

    run_rake_task('nico:sync')
  end

  it 'サムネール取得失敗後も次回同期で再試行できる' do
    post = Post.create!(
        title: 'old',
        url: 'https://www.nicovideo.jp/watch/sm9',
        uploaded_user: nil)
    Tag.bot
    Tag.tagme

    stub_python([{ 'code' => 'sm9', 'title' => 't', 'tags' => [] }])
    allow(URI).to receive(:open) do
      StringIO.new(
          '<meta name="thumbnail" content="https://example.com/thumb.jpg">')
    end
    calls = 0
    allow_any_instance_of(Post).to receive(:attach_thumbnail_from_url!) do
      calls += 1
      raise Post::RemoteThumbnailFetchFailed, 'failed'
    end

    2.times do
      run_rake_task('nico:sync')
    end
    expect(calls).to eq(2)
  end

  it '既存 post にあった古い nico tag は active から外され、履歴として discard される' do
    post = Post.create!(
      title: 'old',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil
    )

    # 旧nicoタグ（今回の同期結果に含まれない）
    old_nico = create_tag!('nico:OLD', category: 'nico')
    old_pt = PostTag.create!(post: post, tag: old_nico)
    expect(old_pt.discarded_at).to be_nil

    # 今回は NEW のみ欲しい
    new_nico = create_tag!('nico:NEW', category: 'nico')

    # bot/tagme 念のため
    Tag.bot
    Tag.tagme

    stub_python([{ 'code' => 'sm9', 'title' => 't', 'tags' => ['NEW'] }])
    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    run_rake_task('nico:sync')

    # OLD は active から外れる（discarded_at が入る）
    old_pts = PostTag.where(post_id: post.id, tag_id: old_nico.id).order(:id).to_a
    expect(old_pts.last.discarded_at).to be_present

    # NEW は active にいる
    post.reload
    active_names = post.tags.joins(:tag_name).pluck('tag_names.name')
    expect(active_names).to include('nico:NEW')
    expect(active_names).not_to include('nico:OLD')
  end

  def snapshot_tags(post)
    post.snapshot_tag_names.join(' ')
  end

  def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil)
    PostVersion.create!(
      post: post,
      version_no: version_no,
      event_type: event_type,
      title: post.title,
      url: post.url,
      thumbnail_base: post.thumbnail_base,
      tags: snapshot_tags(post),
      parent_post_ids: post.snapshot_parent_post_ids.join(' '),
      original_created_from: post.original_created_from,
      original_created_before: post.original_created_before,
      created_at: Time.current,
      created_by_user: created_by_user
    )
  end

  it '新規 post 作成時に version 1 を作る' do
    Tag.bot
    Tag.tagme
    Tag.niconico
    Tag.video
    Tag.no_deerjikist

    stub_python([{
      'code' => 'sm9',
      'title' => 't',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.to change(PostVersion, :count).by(1)

    post = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm9')
    version = post.post_versions.order(:version_no).last

    expect(version.version_no).to eq(1)
    expect(version.event_type).to eq('create')
    expect(version.created_by_user).to be_nil
    expect(version.tags).to eq(snapshot_tags(post.reload))
  end

  it '既存 post の内容または tags が変わったとき update version を作る' do
    post = Post.create!(
      title: 'old',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil
    )

    kept_general = create_tag!('spec_kept', category: 'general')
    PostTag.create!(post: post, tag: kept_general)
    create_post_version_for!(post)

    linked = create_tag!('spec_linked', category: 'general')
    nico = create_tag!('nico:AAA', category: 'nico')
    link_nico_to_tag!(nico, linked)

    Tag.bot
    Tag.tagme
    Tag.no_deerjikist

    stub_python([{
      'code' => 'sm9',
      'title' => 't',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.to change(PostVersion, :count).by(1)

    version = post.reload.post_versions.order(:version_no).last
    expect(version.version_no).to eq(2)
    expect(version.event_type).to eq('update')
    expect(version.created_by_user).to be_nil
    expect(version.tags).to eq(snapshot_tags(post.reload))
  end

  it '既存 post に差分が無いときは新しい version を作らない' do
    nico = create_tag!('nico:AAA', category: 'nico')
    no_deerjikist = create_tag!('ニジラー情報不詳', category: 'meta')

    post = Post.create!(
      title: 't',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil,
      original_created_from: Time.iso8601('2026-01-01T03:34:00Z'),
      original_created_before: Time.iso8601('2026-01-01T03:35:00Z')
    )

    PostTag.create!(post: post, tag: nico)
    PostTag.create!(post: post, tag: no_deerjikist)
    create_post_version_for!(post)

    stub_python([{
      'code' => 'sm9',
      'title' => 't',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.not_to change(PostVersion, :count)

    version = post.reload.post_versions.order(:version_no).last
    expect(version.version_no).to eq(1)
    expect(version.event_type).to eq('create')
    expect(version.tags).to eq(snapshot_tags(post.reload))
  end

  it '新規 nico tag に nico tag version を作る' do
    Tag.bot
    Tag.tagme
    Tag.niconico
    Tag.video
    Tag.no_deerjikist

    stub_python([{
      'code' => 'sm9',
      'title' => 't',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.to change(NicoTagVersion, :count).by(1)

    nico_tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'nico:AAA' })
    version = nico_tag.nico_tag_versions.order(:version_no).last

    expect(version.version_no).to eq(1)
    expect(version.event_type).to eq('create')
    expect(version.name).to eq('nico:AAA')
    expect(version.created_by_user).to be_nil
  end

  it '既存 post に version が無い場合は create snapshot を補う' do
    post = Post.create!(
      title: 'old',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil
    )

    kept_general = create_tag!('spec_kept_without_version', category: 'general')
    PostTag.create!(post: post, tag: kept_general)

    Tag.bot
    Tag.tagme
    Tag.no_deerjikist

    stub_python([{
      'code' => 'sm9',
      'title' => 'changed title',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.to change { post.reload.post_versions.count }.by(1)

    versions = post.reload.post_versions.order(:version_no)

    expect(versions.map(&:event_type)).to eq(['create'])
    expect(versions.first.title).to eq('changed title')
    expect(versions.first.tags).to eq(snapshot_tags(post.reload))
  end

  it '既存 version がある post には update version を作る' do
    post = Post.create!(
      title: 'old',
      url: 'https://www.nicovideo.jp/watch/sm9',
      uploaded_user: nil
    )

    kept_general = create_tag!('spec_kept_with_version', category: 'general')
    PostTag.create!(post: post, tag: kept_general)

    PostVersionRecorder.record!(
      post: post,
      event_type: :create,
      created_by_user: nil
    )

    Tag.bot
    Tag.tagme
    Tag.no_deerjikist

    stub_python([{
      'code' => 'sm9',
      'title' => 'changed title',
      'tags' => ['AAA'],
      'uploaded_at' => '2026-01-01 12:34:56'
    }])

    allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

    expect {
      run_rake_task('nico:sync')
    }.to change { post.reload.post_versions.count }.by(1)

    versions = post.reload.post_versions.order(:version_no)

    expect(versions.map(&:event_type)).to eq(['create', 'update'])
    expect(versions.first.title).to eq('old')
    expect(versions.second.title).to eq('changed title')
    expect(versions.second.tags).to eq(snapshot_tags(post.reload))
  end
end
