require 'rails_helper'


RSpec.describe 'NicoTags', type: :request do
  describe 'GET /tags/nico' do
    it 'returns paginated tags and total count' do
      create_list(:tag, 3, :nico)

      get '/tags/nico', params: { page: 2, limit: 2 }

      expect(response).to have_http_status(:ok)
      expect(json['tags'].size).to eq(1)
      expect(json['count']).to eq(3)
    end

    it 'filters by nico tag name, linked tag name, and link status' do
      linked = create(:tag, :nico)
      linked.tag_name.update!(name: 'nico:search_linked')
      unlinked = create(:tag, :nico)
      unlinked.tag_name.update!(name: 'nico:search_unlinked')
      other = create(:tag, :nico)
      other.tag_name.update!(name: 'nico:other')
      destination = create(:tag, :general)
      destination.tag_name.update!(name: 'destination_search')
      NicoTagRelation.create!(nico_tag: linked, tag: destination)
      NicoTagRelation.create!(nico_tag: other, tag: create(:tag, :general))

      get '/tags/nico', params: {
        name: 'search_',
        linked_tag: 'destination_',
        link_status: 'linked'
      }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(1)
      expect(json.fetch('tags').map { |tag| tag['id'] }).to eq([linked.id])

      get '/tags/nico', params: { name: 'search_', link_status: 'unlinked' }

      expect(json['count']).to eq(1)
      expect(json.fetch('tags').map { |tag| tag['id'] }).to eq([unlinked.id])
    end

    it 'sorts by name and timestamps' do
      older = create(:tag, :nico)
      older.tag_name.update!(name: 'nico:a')
      older.update_columns(created_at: 2.days.ago)
      newer = create(:tag, :nico)
      newer.tag_name.update!(name: 'nico:b')
      newer.update_columns(created_at: 1.day.ago)
      older_post_tag =
        PostTag.create!(post: Post.create!(url: 'https://example.com/nico-older'), tag: older)
      older_post_tag.update_columns(created_at: 1.hour.ago)
      newer_post_tag =
        PostTag.create!(post: Post.create!(url: 'https://example.com/nico-newer'), tag: newer)
      newer_post_tag.update_columns(created_at: 2.hours.ago)

      get '/tags/nico', params: { order: 'name:desc' }
      expect(json.fetch('tags').map { |tag| tag['id'] }).to eq([newer.id, older.id])

      get '/tags/nico', params: { order: 'created_at:asc' }
      expect(json.fetch('tags').map { |tag| tag['id'] }).to eq([older.id, newer.id])

      get '/tags/nico', params: { order: 'updated_at:desc' }
      expect(json.fetch('tags').map { |tag| tag['id'] }).to eq([older.id, newer.id])
      expect(Time.zone.parse(json.fetch('tags').first.fetch('recent_post_tag_created_at')))
        .to be_within(1.second).of(older_post_tag.created_at)
    end
  end

  describe 'PATCH /tags/nico/:id' do
    let(:member) { create(:user, :member) }
    let(:admin) { create(:user, :admin) }
    let(:nico_tag) { create(:tag, :nico) }

    it '401 when not logged in' do
      sign_out
      patch "/tags/nico/#{nico_tag.id}", params: { tags: 'a b' }
      expect(response).to have_http_status(:unauthorized)
    end

    it '403 when not member' do
      sign_in_as(create(:user, role: 'guest'))
      patch "/tags/nico/#{nico_tag.id}", params: { tags: 'a b' }
      expect(response).to have_http_status(:forbidden)
    end

    it '400 when target is not nico category' do
      sign_in_as(member)
      non_nico = create(:tag, :general)
      patch "/tags/nico/#{non_nico.id}", params: { tags: 'a b' }
      expect(response).to have_http_status(:bad_request)
    end

    it '200 and updates linked tags while recording tag versions' do
      sign_in_as(admin)

      nico_tag_name = TagName.create!(name: 'nico:nico_tags_spec_source')
      nico_tag = Tag.create!(tag_name: nico_tag_name, category: :nico)

      linked_a_name = TagName.create!(name: 'nico_linked_a')
      linked_a = Tag.create!(tag_name: linked_a_name, category: :general)

      linked_b_name = TagName.create!(name: 'nico_linked_b')
      linked_b = Tag.create!(tag_name: linked_b_name, category: :general)

      TagVersioning.ensure_snapshot!(nico_tag, created_by_user: admin)

      expect {
        patch "/tags/nico/#{nico_tag.id}", params: {
          tags: "  #{linked_a.name}\n#{linked_b.name}  "
        }
      }.to change(TagVersion, :count).by(2)
       .and change(NicoTagVersion, :count).by(1)

      expect(response).to have_http_status(:ok)

      names = json.map { |t| t['name'] }
      expect(names).to match_array(['nico_linked_a', 'nico_linked_b'])

      linked_versions = TagVersion.where(tag: [linked_a, linked_b]).order(:tag_id)
      expect(linked_versions.map(&:event_type)).to eq(['create', 'create'])
      expect(linked_versions.map(&:created_by_user_id)).to all(eq(admin.id))

      versions = nico_tag.reload.nico_tag_versions.order(:version_no)
      expect(versions.map(&:event_type)).to eq(['create', 'update'])
      expect(versions.last.linked_tags.split).to match_array([
        'nico_linked_a',
        'nico_linked_b'
      ])
      expect(versions.last.created_by_user_id).to eq(admin.id)
    end

    it 'returns 422 when linked tag normalises to nico tag' do
      sign_in_as(member)

      other_nico = create(:tag, :nico, name: 'nico:linked_ng')
      TagName.create!(name: 'linked_ng_alias', canonical: other_nico.tag_name)

      TagVersioning.ensure_snapshot!(nico_tag, created_by_user: member)

      expect {
        patch "/tags/nico/#{nico_tag.id}", params: { tags: 'linked_ng_alias' }
      }.not_to change(NicoTagVersion, :count)

      expect(response).to have_http_status(:unprocessable_entity)
      expect(json.fetch('errors')).to include(
          'tags' => ['ニコニコ・タグ同士は連携できません．'])
    end

    it 'returns the tags field error when a nico tag is specified directly' do
      sign_in_as(member)

      patch "/tags/nico/#{nico_tag.id}", params: { tags: 'nico:linked_ng' }

      expect(response).to have_http_status(:unprocessable_entity)
      expect(json.fetch('errors')).to include(
          'tags' => ['ニコニコ・タグ同士は連携できません．'])
    end

    it 'returns tag name validation errors on the tags field and rolls back created tags' do
      sign_in_as(member)
      TagNameSanitisationRule.create!(
        priority: 1,
        source_pattern: 'invalid',
        replacement: 'valid'
      )
      nico_tag

      expect {
        patch "/tags/nico/#{nico_tag.id}", params: { tags: 'created_first invalid' }
      }.not_to change(TagName, :count)

      expect(response).to have_http_status(:unprocessable_entity)
      expect(json.fetch('errors').fetch('tags')).to include(
          a_string_including('タグ名 “invalid”:', '名前に使用できない文字が含まれてゐます．'))
    end
  end
end
