require "pact_broker/pacts/pact_publication"
require "sequel/plugins/upsert"
require "sequel"

module Sequel
  module Plugins
    module Upsert
      class PacticipantNoUpsert < Sequel::Model(:pacticipants)
        plugin :timestamps, update_on_create: true
      end

      class Pacticipant < Sequel::Model
        plugin :upsert, identifying_columns: [:name]
        plugin :timestamps, update_on_create: true
      end

      class Version < Sequel::Model
        plugin :upsert, identifying_columns: [:pacticipant_id, :number]
        plugin :timestamps, update_on_create: true
      end

      class LatestPactPublicationIdForConsumerVersion < Sequel::Model(:latest_pact_publication_ids_for_consumer_versions)
        set_primary_key [:provider_id, :consumer_version_id]
        unrestrict_primary_key
        plugin :upsert, identifying_columns: [:provider_id, :consumer_version_id]
      end

      describe PacticipantNoUpsert do
        it "has an _insert_dataset method" do
          expect(PacticipantNoUpsert.private_instance_methods).to include(:_insert_dataset)
        end
      end

      describe "LatestPactPublicationIdForConsumerVersion" do
        before do
          td.create_pact_with_hierarchy("Foo", "1", "Bar")
            .create_consumer_version("2")
        end

        let!(:new_pact_publication) do
          pact_publication_values = ::PactBroker::Pacts::PactPublication.first.values.dup
          pact_publication_values.delete(:id)
          pact_publication_values.delete(:created_at)
          pact_publication_values.delete(:updated_at)
          pact_publication_values[:revision_number] = 2
          PactBroker::Pacts::PactPublication.new(pact_publication_values).save
        end

        let(:new_latest_pact_publication_id_for_consumer_version) do
          values = LatestPactPublicationIdForConsumerVersion.first.values
          LatestPactPublicationIdForConsumerVersion.new(values.merge(pact_publication_id: new_pact_publication.id))
        end

        describe "save" do
          subject { new_latest_pact_publication_id_for_consumer_version.save }

          it "raises an error" do
            expect { subject }.to raise_error Sequel::UniqueConstraintViolation
          end
        end

        describe "upsert" do
          subject { new_latest_pact_publication_id_for_consumer_version.upsert }

          it "updates the new object with the values from the existing object" do
            expect(subject.pact_publication_id).to eq new_pact_publication.id
          end
        end
      end

      context "when a duplicate is inserted with no upsert" do
        before do
          PacticipantNoUpsert.new(name: "Foo").save
        end

        subject do
          PacticipantNoUpsert.new(name: "Foo").save
        end

        it "raises an error" do
          expect { subject }.to raise_error Sequel::UniqueConstraintViolation
        end
      end

      # This doesn't work on MSQL because the _insert_raw method
      # does not return the row ID of the duplicated row when upsert is used
      # May have to go back to the old method of doing this
      context "when a duplicate Pacticipant is inserted with upsert" do
        before do
          Pacticipant.new(name: "Foo", repository_url: "http://foo").upsert
        end

        subject do
          Pacticipant.new(name: "Foo", repository_url: "http://bar").upsert
        end

        it "does not raise an error" do
          expect { subject }.to_not raise_error
        end

        it "sets the values on the object" do
          expect(subject.repository_url).to eq "http://bar"
        end

        it "does not insert another row" do
          expect { subject }.to_not change { Pacticipant.count }
        end
      end

      context "when a duplicate Version is inserted with upsert" do
        let!(:pacticipant) { Pacticipant.new(name: "Foo").save }
        let!(:original_version) do
          version = Version.new(
            number: "1",
            pacticipant_id: pacticipant.id,
            build_url: "original-url"
          ).upsert
          Version.where(id: version.id).update(created_at: yesterday, updated_at: yesterday)
          version
        end
        let(:yesterday) { DateTime.now - 2 }

        subject do
          Version.new(number: "1", pacticipant_id: pacticipant.id, build_url: "new-url").upsert
        end

        it "does not raise an error" do
          expect { subject }.to_not raise_error
        end

        it "sets the values on the object" do
          expect(subject.build_url).to eq "new-url"
        end

        context "when an attribute is not set" do
          subject do
            Version.new(number: "1", pacticipant_id: pacticipant.id).upsert
          end

          it "nils out values that weren't set on the second model" do
            expect(subject.build_url).to eq nil
          end
        end


        it "does not insert another row" do
          expect { subject }.to_not change { Version.count }
        end

        it "does not change the created_at" do
          expect { subject }.to_not change { Version.where(number: "1").first.created_at }
        end

        it "does change the updated_at" do
          expect { subject }.to change { Version.where(number: "1").first.updated_at }
        end
      end
    end
  end
end
