shared_examples_for "a Ridley Resource" do |resource_klass|
  let(:connection) { double('connection', hosted?: true) }
  let(:client) { double('client', connection: connection) }
  let(:active_connection) { double('active-connection') }
  let(:response) { double('response') }

  describe "ClassMethods" do
    subject { resource_klass }

    describe "::all" do
      it "sends a get request for the class' resource_path using the given client" do
        allow(response).to receive(:body) { Hash.new }
        expect(client.connection).to receive(:get).with(subject.resource_path).and_return(response)

        subject.all(client)
      end
    end

    describe "::find" do
      it "sends a get request to the given client to the resource_path of the class for the given chef_id" do
        chef_id = "ridley_test"
        allow(response).to receive(:body) { Hash.new }
        expect(client.connection).to receive(:get).with("#{subject.resource_path}/#{chef_id}").and_return(response)

        subject.find(client, chef_id)
      end
    end

    describe "::create" do
      it "sends a post request to the given client using the includer's resource_path" do
        attrs = {
          first_name: "jamie",
          last_name: "winsor"
        }

        allow(response).to receive(:body) { attrs }
        expect(client.connection).to receive(:post).with(subject.resource_path, duck_type(:to_json)).and_return(response)

        subject.create(client, attrs)
      end
    end

    describe "::delete" do
      it "sends a delete request to the given client using the includer's resource_path for the given string" do
        allow(response).to receive(:body) { Hash.new }
        expect(client.connection).to receive(:delete).with("#{subject.resource_path}/ridley-test").and_return(response)

        subject.delete(client, "ridley-test")
      end

      it "accepts an object that responds to 'chef_id'" do
        object = double("obj")
        allow(object).to receive(:chef_id) { "hello" }
        allow(response).to receive(:body) { Hash.new }
        expect(client.connection).to receive(:delete).with("#{subject.resource_path}/#{object.chef_id}").and_return(response)

        subject.delete(client, object)
      end
    end

    describe "::delete_all" do
      it "sends a delete request for every object in the collection" do
        skip
      end
    end

    describe "::update" do
      it "sends a put request to the given client using the includer's resource_path with the given object" do
        allow(subject).to receive(:chef_id) { :name }
        subject.attribute(:name)
        object = subject.new(name: "hello")
        allow(response).to receive(:body) { Hash.new }
        expect(client.connection).to receive(:put).with("#{subject.resource_path}/#{object.chef_id}", duck_type(:to_json)).and_return(response)

        subject.update(client, object)
      end
    end
  end

  subject { resource_klass.new(client) }

  describe "#save" do
    context "when the object is valid" do
      before(:each) { allow(subject).to receive(:valid?).and_return(true) }

      it "sends a create message to the implementing class" do
        updated = double('updated')
        allow(updated).to receive(:_attributes_).and_return(Hash.new)
        expect(subject.class).to receive(:create).with(client, subject).and_return(updated)

        subject.save
      end

      context "when there is an HTTPConflict" do
        it "sends the update message to self" do
          updated = double('updated')
          allow(updated).to receive(:[]).and_return(Hash.new)
          allow(updated).to receive(:_attributes_).and_return(Hash.new)
          expect(subject.class).to receive(:create).and_raise(Ridley::Errors::HTTPConflict.new(updated))
          expect(subject).to receive(:update).and_return(updated)

          subject.save
        end
      end
    end

    context "when the object is invalid" do
      before(:each) { allow(subject).to receive(:valid?).and_return(false) }

      it "raises an InvalidResource error" do
        expect {
          subject.save
        }.to raise_error(Ridley::Errors::InvalidResource)
      end
    end
  end

  describe "#update" do
    context "when the object is valid" do
      let(:updated) do
        updated = double('updated')
        allow(updated).to receive(:[]).and_return(Hash.new)
        allow(updated).to receive(:_attributes_).and_return(Hash.new)
        updated
      end

      before(:each) { allow(subject).to receive(:valid?).and_return(true) }

      it "sends an update message to the implementing class" do
        expect(subject.class).to receive(:update).with(anything, subject).and_return(updated)
        subject.update
      end

      it "returns true" do
        expect(subject.class).to receive(:update).with(anything, subject).and_return(updated)
        expect(subject.update).to eql(true)
      end
    end

    context "when the object is invalid" do
      before(:each) { allow(subject).to receive(:valid?).and_return(false) }

      it "raises an InvalidResource error" do
        expect {
          subject.update
        }.to raise_error(Ridley::Errors::InvalidResource)
      end
    end
  end

  describe "#chef_id" do
    it "returns the value of the chef_id attribute" do
      subject.class.attribute(:name)
      allow(subject.class).to receive(:chef_id) { :name }
      subject.mass_assign(name: "reset")

      expect(subject.chef_id).to eql("reset")
    end
  end

  describe "#reload" do
    let(:updated_subject) { double('updated_subject', _attributes_: { fake_attribute: "some_value" }) }

    before(:each) do
      subject.class.attribute(:fake_attribute)
      allow(subject.class).to receive(:find).with(client, subject).and_return(updated_subject)
    end

    it "returns itself" do
      expect(subject.reload).to eql(subject)
    end

    it "sets the attributes of self to include those of the reloaded object" do
      subject.reload

      expect(subject.get_attribute(:fake_attribute)).to eql("some_value")
    end
  end
end
