# frozen_string_literal: true

require 'rails_helper'

RSpec.describe FeaturedTag do
  describe 'Normalizations' do
    describe 'name' do
      it { is_expected.to normalize(:name).from('  #hashtag  ').to('hashtag') }
    end
  end

  describe 'Validations' do
    context 'when account already has a featured tag' do
      subject { Fabricate.build :featured_tag, account: account }

      before { Fabricate :featured_tag, account: account, name: 'Test' }

      let(:account) { Fabricate :account }

      it { is_expected.to_not allow_value('Test').for(:name).against(:tag_id) }

      context 'when account has hit limit' do
        before { stub_const 'FeaturedTag::LIMIT', 1 }

        context 'with a local account' do
          let(:account) { Fabricate :account, domain: nil }

          it { is_expected.to_not allow_value(account).for(:account).against(:base).with_message(I18n.t('featured_tags.errors.limit')) }
        end

        context 'with a remote account' do
          let(:account) { Fabricate :account, domain: 'host.example' }

          it { is_expected.to allow_value(account).for(:account) }
        end
      end
    end
  end

  describe 'Callback to set the tag' do
    context 'with no matching tag' do
      it 'creates a new tag' do
        expect { Fabricate :featured_tag, name: 'tag' }
          .to change(Tag, :count).by(1)
      end
    end

    context 'with a matching tag' do
      it 'creates a new tag' do
        tag = Fabricate :tag, name: 'tag'

        expect { Fabricate :featured_tag, name: 'tag' }
          .to_not change(Tag, :count)

        expect(described_class.last.tag)
          .to eq(tag)
      end
    end
  end

  describe 'Callback to set the stats' do
    context 'when no statuses are relevant' do
      it 'sets values to nil' do
        featured_tag = Fabricate :featured_tag

        expect(featured_tag)
          .to have_attributes(
            statuses_count: 0,
            last_status_at: be_nil
          )
      end
    end

    context 'when some statuses are relevant' do
      it 'sets values to nil' do
        tag = Fabricate :tag, name: 'test'
        status = Fabricate :status, visibility: :public, created_at: 10.days.ago
        status.tags << tag

        featured_tag = Fabricate :featured_tag, name: 'test', account: status.account

        expect(featured_tag)
          .to have_attributes(
            statuses_count: 1,
            last_status_at: be_within(0.1).of(status.created_at)
          )
      end
    end
  end

  describe '#sign?' do
    it { is_expected.to be_sign }
  end

  describe '#display_name' do
    subject { featured_tag.display_name }

    let(:featured_tag) { Fabricate.build :featured_tag, name: name, tag: tag }

    context 'with a name value present on the featured tag' do
      let(:name) { 'FeaturedTagName' }
      let(:tag) { nil }

      it { is_expected.to eq('FeaturedTagName') }
    end

    context 'with a missing name value but a present linked tag' do
      let(:name) { nil }
      let(:tag) { Fabricate.build :tag, display_name: 'LinkedTagDisplayName' }

      it { is_expected.to eq('LinkedTagDisplayName') }
    end
  end

  describe '#increment' do
    it 'increases the count and updates the last_status_at timestamp' do
      featured_tag = Fabricate :featured_tag
      timestamp = 5.days.ago

      expect { featured_tag.increment(timestamp) }
        .to change(featured_tag, :statuses_count).from(0).to(1)
        .and change(featured_tag, :last_status_at).from(nil).to(be_within(0.1).of(timestamp))
    end
  end

  describe '#decrement' do
    let(:tag) { Fabricate(:tag, name: 'test') }
    let(:account) { Fabricate(:account) }
    let(:featured_tag) { Fabricate(:featured_tag, name: 'test', account: account) }

    context 'when removing the last status using the tag' do
      let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }

      before do
        status.tags << tag
      end

      it 'decreases the count and updates the last_status_at timestamp' do
        expect { featured_tag.decrement(status) }
          .to change(featured_tag, :statuses_count).from(1).to(0)
          .and change(featured_tag, :last_status_at).to(nil)
      end
    end

    context 'when removing a previous status using the tag' do
      let(:previous_status) { Fabricate(:status, visibility: :public, account: account, created_at: 1.month.ago) }
      let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }

      before do
        previous_status.tags << tag
        status.tags << tag
      end

      it 'decreases the count and updates the last_status_at timestamp' do
        expect { featured_tag.decrement(previous_status) }
          .to change(featured_tag, :statuses_count).from(2).to(1)
          .and not_change(featured_tag, :last_status_at)
      end
    end

    context 'when removing the most recent use of the tag' do
      let(:previous_status) { Fabricate(:status, visibility: :public, account: account, created_at: 1.month.ago) }
      let(:status) { Fabricate(:status, visibility: :public, account: account, created_at: 10.days.ago) }

      before do
        previous_status.tags << tag
        status.tags << tag
      end

      it 'decreases the count and updates the last_status_at timestamp' do
        expect { featured_tag.decrement(status) }
          .to change(featured_tag, :statuses_count).from(2).to(1)
          .and change(featured_tag, :last_status_at)
      end
    end
  end
end
