require 'rails_helper'

RSpec.describe TagNameSanitisationRule, type: :model do
  before do
    described_class.unscoped.delete_all
  end

  describe '.sanitise' do
    before do
      described_class.create!(priority: 10, source_pattern: '_', replacement: '')
      described_class.create!(priority: 20, source_pattern: 'ABC', replacement: 'abc')
    end

    it 'applies sanitisation rules sequentially in priority order' do
      expect(described_class.sanitise('A_B_C')).to eq('abc')
    end

    it 'does not fail when a rule does not match' do
      expect { described_class.sanitise('xyz') }.not_to raise_error
      expect(described_class.sanitise('xyz')).to eq('xyz')
    end
  end

  describe 'validations' do
    it 'is invalid when source_pattern is not a valid regexp' do
      rule = described_class.new(priority: 10, source_pattern: '[', replacement: '')
      expect(rule).to be_invalid
      expect(rule.errors[:source_pattern]).to be_present
    end
  end

  describe '.apply!' do
    before do
      described_class.create!(priority: 10, source_pattern: '_', replacement: '')
    end

    context 'when no conflicting tag_name exists' do
      let!(:tag_name) do
        TagName.create!(name: 'tmp').tap do |tn|
          tn.update_columns(name: 'foo_bar', updated_at: Time.current)
        end
      end

      it 'renames the tag_name' do
        described_class.apply!
        expect(tag_name.reload.name).to eq('foobar')
      end
    end

    context 'when a conflicting canonical tag_name exists' do
      let!(:existing) { TagName.create!(name: 'foobar') }
      let!(:source) do
        TagName.create!(name: 'tmp').tap do |tn|
          tn.update_columns(name: 'foo_bar', updated_at: Time.current)
        end
      end

      it 'deletes the source tag_name' do
        described_class.apply!
        expect(TagName.exists?(source.id)).to be(false)
        expect(existing.reload.name).to eq('foobar')
      end
    end

    context 'when the source tag_name has a tag and the existing one has no tag' do
      let!(:existing) { TagName.create!(name: 'foobar') }
      let!(:source_tag) { create(:tag, name: 'tmp', category: :general) }
      let!(:source_tag_name_id) { source_tag.tag_name_id }

      before do
        source_tag.tag_name.update_columns(name: 'foo_bar', updated_at: Time.current)
      end

      it 'moves the tag to the existing tag_name' do
        described_class.apply!
        expected_tag_name_id = existing.canonical_id || existing.id
        expect(source_tag.reload.tag_name_id).to eq(expected_tag_name_id)
        expect(TagName.exists?(source_tag_name_id)).to be(false)
      end
    end

    context 'when both source and existing tag_names have tags' do
      let!(:existing_tn) { TagName.create!(name: 'foobar') }
      let!(:existing_tag) { Tag.create!(tag_name: existing_tn, category: :general) }

      let!(:source_tn) { TagName.create!(name: 'tmp') }
      let!(:source_tag) { Tag.create!(tag_name: source_tn, category: :general) }
      let!(:source_tag_name_id) { source_tn.id }

      before do
        source_tn.update_columns(name: 'foo_bar', updated_at: Time.current)
      end

      it 'merges the source tag into the existing tag and deletes the source tag_name' do
        expect(TagName.find_by(name: 'foobar')&.tag&.id).to eq(existing_tag.id)
        expect(TagName.find_by(name: 'foo_bar')&.tag&.id).to eq(source_tag.id)

        described_class.apply!

        expect(Tag.exists?(source_tag.id)).to be(false)
        expect(TagName.exists?(source_tag.tag_name_id)).to be(false)
      end
    end
  end
end
