require "pact_broker/pacts/service"
require "pact_broker/pacts/pact_params"
require "pact_broker/webhooks/execution_configuration"

module PactBroker
  module Pacts
    describe Service do
      describe "create_or_update_pact" do
        include_context "stubbed repositories"

        before do
          allow(pacticipant_repository).to receive(:find_by_name_or_create).with(params[:consumer_name]).and_return(consumer)
          allow(pacticipant_repository).to receive(:find_by_name_or_create).with(params[:provider_name]).and_return(provider)
          allow(version_repository).to receive(:find_by_pacticipant_id_and_number_or_create).and_return(version)
          allow(pact_repository).to receive(:find_by_version_and_provider).and_return(existing_pact)
          allow(pact_repository).to receive(:create).and_return(new_pact)
          allow(pact_repository).to receive(:update).and_return(new_pact)
          allow(pact_repository).to receive(:find_previous_pacts).and_return(previous_pacts)
        end

        let(:consumer) { double("consumer", id: 1) }
        let(:provider) { double("provider", id: 2) }
        let(:version) { double("version", id: 3, pacticipant_id: 1) }
        let(:existing_pact) { nil }
        let(:new_pact) { double("new_pact", consumer_version_tag_names: %w[dev], json_content: json_content, pact_version_sha: "1", consumer_name: "Foo", consumer_version_number: "2") }
        let(:json_content) { { the: "contract" }.to_json }
        let(:json_content_with_ids) { { the: "contract with ids" }.to_json }
        let(:previous_pacts) { [] }
        let(:params) do
          {
            consumer_name: "Foo",
            provider_name: "Bar",
            consumer_version_number: "1",
            json_content: json_content,
            pact_version_sha: PactBroker::Pacts::GenerateSha.call(json_content)
          }
        end
        let(:content) { double("content") }
        let(:content_with_interaction_ids) { double("content_with_interaction_ids", to_json: json_content_with_ids) }
        let(:expected_event_context) { { consumer_version_tags: ["dev"] } }

        before do
          allow(Content).to receive(:from_json).and_return(content)
          allow(content).to receive(:with_ids).and_return(content_with_interaction_ids)
          allow(PactBroker::Pacts::GenerateSha).to receive(:call).and_call_original
          allow(Service).to receive(:broadcast)
        end

        subject { Service.create_or_update_pact(params) }

        context "when no pact exists with the same params" do
          it "saves the pact interactions/messages with ids added to them" do
            expect(pact_repository).to receive(:create).with hash_including(json_content: json_content_with_ids)
            subject
          end

          # TODO test all this contract_content_changed logic properly!
          context "when the latest pact for one of the tags has a different pact_version_sha" do
            before do
              allow(pact_repository).to receive(:find_previous_pacts).and_return(previous_pacts_by_tag)
            end

            let(:previous_dev_pact_version_sha) { "2" }
            let(:previous_pacts_by_tag) do
              {
                dev: double("previous pact", pact_version_sha: previous_dev_pact_version_sha)
              }
            end

            it "broadcasts the contract_published event" do
              expect(Service).to receive(:broadcast).with(
                :contract_published,
                  {
                    pact: new_pact,
                    event_context: { consumer_version_tags: %w[dev] }
                  }
              )
              subject
            end

            it "broadcasts the contract_content_changed event" do
              expect(Service).to receive(:broadcast).with(
                :contract_content_changed,
                  {
                    pact: new_pact,
                    event_comment: "pact content has changed since the last consumer version tagged with dev",
                    event_context: { consumer_version_tags: %w[dev] }
                  }
              )
              subject
            end
          end

          context "when the new pact has not changed content or tags since the previous version with the same tags" do
            before do
              allow(pact_repository).to receive(:find_previous_pacts).and_return(previous_pacts_by_tag)
            end

            let(:previous_dev_pact_version_sha) { "1" }
            let(:previous_pacts_by_tag) do
              {
                dev: double("previous pact", pact_version_sha: previous_dev_pact_version_sha)
              }
            end

            it "broadcasts the contract_published event" do
              expect(Service).to receive(:broadcast).with(
                :contract_published,
                  {
                    pact: new_pact,
                    event_comment: "pact content is the same as previous version with tag dev and no new tags were applied",
                    event_context: { consumer_version_tags: %w[dev] }
                  }
              )
              subject
            end
          end
        end

        context "when a pact exists with the same params" do
          let(:existing_pact) do
            double("existing_pact",
              id: 4,
              consumer_version_tag_names: %[dev],
              json_content: { the: "contract" }.to_json,
              pact_version_sha: pact_version_sha
            )
          end
          let(:pact_version_sha) { "1" }

          let(:expected_event_context) { { consumer_version_tags: ["dev"] } }

          it "saves the pact interactions/messages with ids added to them" do
            expect(pact_repository).to receive(:update).with(anything, hash_including(json_content: json_content_with_ids))
            subject
          end

          it "broadcasts the contract_published event" do
            expect(Service).to receive(:broadcast).with(:contract_published, { pact: new_pact, event_context: { consumer_version_tags: %w[dev] } })
            subject
          end

          context "when the pact_version_sha is different" do
            let(:pact_version_sha) { "2" }

            it "broadcasts the contract_content_changed event" do
              expect(Service).to receive(:broadcast).with(
                :contract_content_changed,
                  {
                    pact: new_pact,
                    event_comment: "pact content modified since previous publication for Foo version 2",
                    event_context: { consumer_version_tags: %w[dev] }
                  }
              )
              subject
            end
          end

          context "when the pact_version_sha is the same" do
            it "broadcasts the contract_content_unchanged event" do
              expect(Service).to receive(:broadcast).with(
                :contract_content_unchanged,
                  {
                    pact: new_pact,
                    event_comment: "pact content was unchanged",
                    event_context: { consumer_version_tags: %w[dev] }
                  }
              )
              subject
            end
          end
        end
      end

      describe "find_distinct_pacts_between" do
        let(:pact_1) { double("pact 1", json_content: "content 1")}
        let(:pact_2) { double("pact 2", json_content: "content 2")}
        let(:pact_3) { double("pact 3", json_content: "content 2")}
        let(:pact_4) { double("pact 4", json_content: "content 3")}

        let(:all_pacts) { [pact_4, pact_3, pact_2, pact_1]}

        before do
          allow_any_instance_of(Pacts::Repository).to receive(:find_all_pact_versions_between).and_return(all_pacts)
        end

        subject { Service.find_distinct_pacts_between "consumer", :and => "provider" }

        it "returns the distinct pacts" do
          expect(subject).to eq [pact_4, pact_2, pact_1]
        end
      end

      describe "delete" do
        before do
          td.create_pact_with_hierarchy
            .create_webhook
            .create_triggered_webhook
            .create_webhook_execution
            .revise_pact
        end

        let(:params) do
          {
            consumer_name: td.consumer.name,
            provider_name: td.provider.name,
            consumer_version_number: td.consumer_version.number
          }
        end

        subject { Service.delete PactParams.new(PactBroker::Pacts::PactParams.new(params)) }

        it "deletes the pact" do
          expect { subject }.to change {
            Pacts::PactPublication.where(id: td.pact.id ).count
          }.by(-1)
        end
      end

      describe "find_for_verification integration test" do
        before do
          td.create_provider("Bar")
            .create_provider_version
            .create_provider_version_tag("master")
            .add_minute
            .create_pact_with_hierarchy("Foo", "1", "Bar")
            .create_consumer_version_tag("feat-1", comment: "latest for feat-1")
            .create_consumer_version("2")
            .create_pact(comment: "overall latest")
            .create_consumer_version("3")
        end

        let(:options) { { } }

        subject { Service.find_for_verification("Bar", nil, ["master"], Selectors.new, options) }


        context "when the consumer version tags are empty" do
          it "returns the latest overall pact for the consumer" do
            expect(subject.first.consumer_version_number).to eq "2"
            expect(subject.first.wip).to be false
          end
        end

        context "when include_wip_pacts_since is not specified" do
          it "does not include the WIP pacts" do
            expect(subject.size).to eq 1
          end
        end

        context "when WIP pacts are included" do
          let(:options) do
            {
              include_wip_pacts_since: (Date.today - 1).to_datetime
            }
          end

          it "returns the WIP pacts as well as the specified pacts" do
            expect(subject.size).to eq 2
            expect(subject.last.consumer_version_number).to eq "1"
            expect(subject.last.wip).to be true
          end

          context "when the WIP pact has the same content as a specified pact" do
            before do
              td.create_pact_with_hierarchy("Blah", "1", "Wiffle")
                .create_consumer_version_tag("feat-1", comment: "latest for feat-1")
                .create_consumer_version("2")
                .republish_same_pact(comment: "overall latest")
            end

            subject { Service.find_for_verification("Wiffle", "main", ["master"], Selectors.new, options) }

            it "is not included" do
              expect(subject.size).to eq 1
            end
          end
        end
      end

      describe "find_for_verification_publication integration test" do
        before do
          json_content = { interactions: [] }.to_json
          td.create_pact_with_hierarchy("Foo", "1", "Bar", json_content)
            .create_pact_with_hierarchy("Foo", "2", "Bar", json_content)
        end

        let(:consumer_version_selector_hashes) do
          [
            { tag: "foo", latest: true, consumer_version_number: "1"},
            { tag: "bar", latest: true, consumer_version_number: "1"},
            { branch: "beep", latest: true, consumer_version_number: "2"}
          ]
        end

        let(:pact_params) do
          PactParams.new(
            consumer_name: "Foo",
            provider_name: "Bar",
            pact_version_sha: td.and_return(:pact).pact_version_sha
          )
        end

        subject { Service.find_for_verification_publication(pact_params, consumer_version_selector_hashes) }

        it "deduplicates the pacts by consumer version number" do
          expect(subject.size).to eq 2
        end

        it "sets the selectors" do
          expect(subject.first.selectors.size).to eq 2
          expect(subject.first.selectors.first).to have_attributes(latest: true, tag: "bar")
          expect(subject.first.selectors.first.consumer_version).to have_attributes(number: "1")

          expect(subject.first.selectors.last).to have_attributes(latest: true, tag: "foo")
          expect(subject.first.selectors.last.consumer_version).to have_attributes(number: "1")

          expect(subject.last.selectors.size).to eq 1
          expect(subject.last.selectors.first).to have_attributes(latest: true, branch: "beep")
          expect(subject.last.selectors.last.consumer_version).to have_attributes(number: "2")
        end

        context "when there are no consumer version selectors" do
          let(:consumer_version_selector_hashes) { nil }

          it "returns the latest pact for the sha" do
            expect(subject.size).to eq 1
            expect(subject.first.consumer_version_number).to eq "2"
          end
        end

        context "when the consumer version selectors is empty" do
          let(:consumer_version_selector_hashes) { [] }

          it "returns the latest pact for the sha" do
            expect(subject.size).to eq 1
            expect(subject.first.consumer_version_number).to eq "2"
          end
        end
      end
    end
  end
end
