require 'rails_helper'

RSpec.describe Service::Base, type: :service do
  class AbcService < Service::Base
    self.service_endpoint = "http://abc.twitch.tv"
    attributes :a, :b, :c
  end

  class XyzService < Service::Base
    self.service_endpoint = "http://xyz.twitch.tv"
    attributes :x, :y, :z
  end

  class EmptyService < Service::Base
  end

  class PrimaryAttributeService < Service::Base
    attributes :dude

    self.primary_attribute = :dude
  end

  context "#service_endpoint" do
    it "returns unique endpoints for each sub-class" do
      expect(AbcService.service_endpoint).to eql("http://abc.twitch.tv")
      expect(XyzService.service_endpoint).to eql("http://xyz.twitch.tv")
    end
  end

  context "#from_attributes" do
    it "returns a persisted object from hash" do
      options = {
        "a" => "a",
        "b" => "b",
        "c" => "c",
      }

      service = AbcService.from_attributes(options)

      values = [:a, :b, :c].map {|mtd| service.send(mtd)}
      expect(values).to eql(["a", "b", "c"])
      expect(service).to be_persisted
    end
  end

  context "#self.attributes" do
    it "adds getters/setters for the attributes specified" do
      service = AbcService.new
      expect(service.a).to be_nil

      service.a = "A"
      expect(service.a).to eql("A")
    end
  end

  context "#errors" do
    it "Returns errors against the service" do
      service = AbcService.new
      expect(service.errors).to be_instance_of ActiveModel::Errors
    end

    it "Sets errors against the service" do
      errors = ["Name is not valid", "Size too big"]
      service = AbcService.new
      service.errors = errors
      expect(service.errors.full_messages).to eql(errors)
    end

    it "Sets errors given only 1 error" do
      error = "Name is not valid"
      service = AbcService.new
      service.errors = error
      expect(service.errors.full_messages).to eql([error])
    end

    it "Raises error if setting error as nil" do
      service = AbcService.new
      expect { service.errors = nil }.to raise_error(RuntimeError)
    end
  end

  context "#connection" do
    it "returns a Faraday connection with giving endpoint" do
      connection = AbcService.connection
      expect(connection).to be_instance_of(Faraday::Connection)
      expect(connection.url_prefix.to_s).to eql("http://abc.twitch.tv/")
    end

    it "raises exception if service_endpoint is not set" do
      expect { EmptyService.connection }.to raise_error(RuntimeError)
    end
  end

  context "#attributes" do
    it "returns a hash of attributes and their values" do
      options = {
        "a" => "a",
        "b" => "b",
        "c" => "c",
      }

      service = AbcService.new(options)
      expect(service.attributes).to eql("a" => "a", "b" => "b", "c" => "c")

      options = {
        "x" => "x",
        "y" => "y",
        "z" => "z",
      }

      service = XyzService.new(options)
      expect(service.attributes).to eql("x" => "x", "y" => "y", "z" => "z")
    end
  end

  describe '#to_param' do
    subject { service.to_param }

    FAKE_ATTRIBUTE = '5'

    context "with a primary attribute" do
      let(:service) { PrimaryAttributeService.new(:dude => FAKE_ATTRIBUTE) }

      it 'returns the primary attribute' do
        expect(subject).to equal(FAKE_ATTRIBUTE)
      end
    end

    context 'without a primary attribute' do
      let(:service) { EmptyService.new }

      it 'throws an error' do
        expect{subject}.to raise_error(RuntimeError)
      end
    end
  end

  describe '#paginate' do
    subject { AbcService.paginate(records, options) }

    context 'with no records' do
      let(:records) { [] }
      let(:options) { {:total_pages => 10, :total_count => 50, :per_page => 5} }

      it { should be_empty }

      it 'contains the expected metadata' do
        expect(subject.total_pages).to eq(10)
        expect(subject.total_count).to eq(50)
        expect(subject.per_page).to eq(5)
      end
    end

    context 'with records' do
      let(:records) { [AbcService.new, AbcService.new, AbcService.new, AbcService.new, AbcService.new] }
      let(:options) { {:total_pages => 1, :total_count => 5, :per_page => 5} }

      it 'should contain the expected records' do
        expect(subject).to match_array(records)
      end

      it 'contains the expected metadata' do
        expect(subject.total_pages).to eq(1)
        expect(subject.total_count).to eq(5)
        expect(subject.per_page).to eq(5)
      end
    end
  end

  describe '#from_errors' do
    subject { AbcService.from_errors(errors) }

    context 'with an array of errors' do
      let(:errors) { ['error 1', 'error 2'] }

      it 'should instantiate a new object with the expected errors' do
        expect(subject.errors).to be_an_instance_of(ActiveModel::Errors)
        expect(subject.errors[:base]).to match_array(errors)
      end
    end

    context 'with an instance of ActiveModel::Errors' do
      let(:errors) { ActiveModel::Errors.new(AbcService.new) }

      before do
        errors.add(:a, 'cannot be nil')
        errors.add(:b, 'cannot be false')
      end

      it 'should instantiate a new object with the expected errors' do
        expect(subject.errors).to be_an_instance_of(ActiveModel::Errors)
        expect(subject.errors[:a]).to eq(['cannot be nil'])
        expect(subject.errors[:b]).to eq(['cannot be false'])
      end
    end

    context 'with no errors' do
      let(:errors) { nil }

      it 'should instantiate a new object with en empty errors attribute' do
        expect{subject}.to raise_error(RuntimeError, 'Errors not set')
      end
    end
  end
end
