# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Rating::Rate, ':create' do
  let!(:author)  { create :author }
  let!(:article) { create :article }

  context 'with no scopeable' do
    before { described_class.create author: author, extra_scopes: {}, metadata: {}, resource: article, value: 3 }

    context 'when rate does not exist yet' do
      it 'creates a rate entry' do
        rate = described_class.last

        expect(rate.author).to   eq author
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 3
      end

      it 'creates a rating entry' do
        rating = Rating::Rating.last

        expect(rating.average).to  eq 3
        expect(rating.estimate).to eq 3
        expect(rating.resource).to eq article
        expect(rating.sum).to      eq 3
        expect(rating.total).to    eq 1
      end
    end

    context 'when rate already exists' do
      let!(:author_2) { create :author }

      before { described_class.create author: author_2, extra_scopes: {}, metadata: {}, resource: article, value: 4 }

      it 'creates one more rate entry' do
        rates = described_class.where(author: [author, author_2]).order('created_at asc')

        expect(rates.size).to eq 2

        rate = rates[0]

        expect(rate.author).to   eq author
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 3

        rate = rates[1]

        expect(rate.author).to   eq author_2
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 4
      end

      it 'updates the unique rating entry' do
        rating = Rating::Rating.find_by(resource: article)

        expect(rating.average).to  eq 3.5
        expect(rating.estimate).to eq 3.5
        expect(rating.resource).to eq article
        expect(rating.sum).to      eq 7
        expect(rating.total).to    eq 2
      end
    end
  end

  context 'with scopeable' do
    let!(:category) { create :category }

    before do
      described_class.create(
        author:       author,
        extra_scopes: {},
        metadata:     {},
        resource:     article,
        scopeable:    category,
        value:        3
      )
    end

    context 'when rate does not exist yet' do
      it 'creates a rate entry' do
        rate = described_class.last

        expect(rate.author).to    eq author
        expect(rate.resource).to  eq article
        expect(rate.scopeable).to eq category
        expect(rate.value).to     eq 3
      end

      it 'creates a rating entry' do
        rating = Rating::Rating.last

        expect(rating.average).to   eq 3
        expect(rating.estimate).to  eq 3
        expect(rating.resource).to  eq article
        expect(rating.scopeable).to eq category
        expect(rating.sum).to       eq 3
        expect(rating.total).to     eq 1
      end
    end

    context 'when rate already exists' do
      let!(:author_2) { create :author }

      before do
        described_class.create(
          author:       author_2,
          extra_scopes: {},
          metadata:     {},
          resource:     article,
          scopeable:    category,
          value:        4
        )
      end

      it 'creates one more rate entry' do
        rates = described_class.where(author: [author, author_2]).order('created_at asc')

        expect(rates.size).to eq 2

        rate = rates[0]

        expect(rate.author).to    eq author
        expect(rate.resource).to  eq article
        expect(rate.scopeable).to eq category
        expect(rate.value).to     eq 3

        rate = rates[1]

        expect(rate.author).to    eq author_2
        expect(rate.resource).to  eq article
        expect(rate.scopeable).to eq category
        expect(rate.value).to     eq 4
      end

      it 'updates the unique rating entry' do
        rating = Rating::Rating.find_by(resource: article, scopeable: category)

        expect(rating.average).to   eq 3.5
        expect(rating.estimate).to  eq 3.5
        expect(rating.resource).to  eq article
        expect(rating.scopeable).to eq category
        expect(rating.sum).to       eq 7
        expect(rating.total).to     eq 2
      end
    end
  end

  context 'with metadata' do
    context 'with nil value' do
      it 'creates a rate entry ignoring metadata' do
        described_class.create author: author, extra_scopes: {}, metadata: nil, resource: article, value: 3

        rate = described_class.last

        expect(rate.author).to   eq author
        expect(rate.comment).to  eq nil
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 3
      end
    end

    context 'with empty value' do
      it 'creates a rate entry ignoring metadata' do
        described_class.create author: author, extra_scopes: {}, metadata: '', resource: article, value: 3

        rate = described_class.last

        expect(rate.author).to   eq author
        expect(rate.comment).to  eq nil
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 3
      end
    end

    context 'with hash value' do
      it 'creates a rate entry with metadata included' do
        described_class.create(
          author:       author,
          extra_scopes: {},
          metadata:     { comment: 'comment' },
          resource:     article,
          value:        3
        )

        rate = described_class.last

        expect(rate.author).to   eq author
        expect(rate.comment).to  eq 'comment'
        expect(rate.resource).to eq article
        expect(rate.value).to    eq 3
      end
    end
  end

  if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
    context 'with extra scopes' do
      let!(:category) { create :category }

      it 'creates a rate entry' do
        described_class.create(
          author:       author,
          extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
          metadata:     {},
          resource:     article,
          scopeable:    category,
          value:        1
        )

        rate = described_class.last

        expect(rate.author).to    eq author
        expect(rate.resource).to  eq article
        expect(rate.scope_1).to   eq 'scope_1'
        expect(rate.scope_2).to   eq 'scope_2'
        expect(rate.scopeable).to eq category
        expect(rate.value).to     eq 1
      end

      it 'creates a rating entry' do
        described_class.create(
          author:       author,
          extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
          metadata:     {},
          resource:     article,
          scopeable:    category,
          value:        1
        )

        rating = Rating::Rating.last

        expect(rating.average).to   eq 1
        expect(rating.estimate).to  eq 1
        expect(rating.resource).to  eq article
        expect(rating.scopeable).to eq category
        expect(rating.sum).to       eq 1
        expect(rating.total).to     eq 1
      end

      context 'when rate already exists' do
        before do
          described_class.create(
            author:       author,
            extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
            metadata:     {},
            resource:     article,
            scopeable:    category,
            value:        1
          )
        end

        it 'updates the rate entry' do
          described_class.create(
            author:       author,
            extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
            metadata:     {},
            resource:     article,
            scopeable:    category,
            value:        2
          )

          rates = described_class.all

          expect(rates.size).to eq 1

          rate = rates[0]

          expect(rate.author).to    eq author
          expect(rate.resource).to  eq article
          expect(rate.scope_1).to   eq 'scope_1'
          expect(rate.scope_2).to   eq 'scope_2'
          expect(rate.scopeable).to eq category
          expect(rate.value).to     eq 2
        end

        it 'updates the unique rating entry' do
          described_class.create(
            author:       author,
            extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
            metadata:     {},
            resource:     article,
            scopeable:    category,
            value:        2
          )

          ratings = Rating::Rating.all

          expect(ratings.size).to eq 1

          rating = ratings[0]

          expect(rating.average).to   eq 2
          expect(rating.estimate).to  eq 2
          expect(rating.resource).to  eq article
          expect(rating.scopeable).to eq category
          expect(rating.sum).to       eq 2
          expect(rating.total).to     eq 1
        end
      end

      context 'when rate already exists but with at least one extra scope different' do
        before do
          described_class.create(
            author:       author,
            extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
            metadata:     {},
            resource:     article,
            scopeable:    category,
            value:        1
          )

          described_class.create(
            author:       author,
            extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_missing' },
            metadata:     {},
            resource:     article,
            scopeable:    category,
            value:        2
          )
        end

        it 'creates a new rate entry' do
          rates = described_class.all

          expect(rates.size).to eq 2

          rate = rates[0]

          expect(rate.author).to    eq author
          expect(rate.resource).to  eq article
          expect(rate.scope_1).to   eq 'scope_1'
          expect(rate.scope_2).to   eq 'scope_2'
          expect(rate.scopeable).to eq category
          expect(rate.value).to     eq 1

          rate = rates[1]

          expect(rate.author).to    eq author
          expect(rate.resource).to  eq article
          expect(rate.scope_1).to   eq 'scope_1'
          expect(rate.scope_2).to   eq 'scope_missing'
          expect(rate.scopeable).to eq category
          expect(rate.value).to     eq 2
        end

        it 'updates the unique rating entry' do
          ratings = Rating::Rating.all

          expect(ratings.size).to eq 1

          rating = ratings[0]

          expect(rating.average).to   eq 1.5
          expect(rating.estimate).to  eq 1.5
          expect(rating.resource).to  eq article
          expect(rating.scopeable).to eq category
          expect(rating.sum).to       eq 3
          expect(rating.total).to     eq 2
        end
      end
    end
  end
end