require "pact_broker/services"
require "pact_broker/hash_refinements"

module PactBroker
  module Webhooks
    module TriggerService
      RESOURCE_CREATION = PactBroker::Webhooks::TriggeredWebhook::TRIGGER_TYPE_RESOURCE_CREATION
      USER = PactBroker::Webhooks::TriggeredWebhook::TRIGGER_TYPE_USER

      extend self
      extend PactBroker::Repositories
      extend PactBroker::Services
      include PactBroker::Logging
      using PactBroker::HashRefinements

      def next_uuid
        SecureRandom.uuid
      end

      # TODO support currently deployed
      def test_execution webhook, event_context, execution_configuration
        merged_options = execution_configuration.with_failure_log_message("Webhook execution failed").to_hash

        verification = nil
        if webhook.trigger_on_provider_verification_published?
          verification = verification_service.search_for_latest(webhook.consumer_name, webhook.provider_name) || PactBroker::Verifications::PlaceholderVerification.new
        end

        pact = pact_service.search_for_latest_pact(consumer_name: webhook.consumer_name, provider_name: webhook.provider_name) || PactBroker::Pacts::PlaceholderPact.new
        webhook.execute(pact, verification, event_context.merge(event_name: "test"), merged_options)
      end

      def execute_triggered_webhook_now triggered_webhook, webhook_execution_configuration_hash
        webhook_execution_result = triggered_webhook.execute(webhook_execution_configuration_hash)
        webhook_repository.create_execution(triggered_webhook, webhook_execution_result)
        webhook_execution_result
      end

      # the main entry point
      def create_triggered_webhooks_for_event pact, verification, event_name, event_context
        webhooks = webhook_repository.find_webhooks_to_trigger(consumer: pact.consumer, provider: pact.provider, event_name: event_name)

        if webhooks.any?
          create_triggered_webhooks_for_webhooks(webhooks, pact, verification, event_name, event_context.merge(event_name: event_name))
        else
          []
        end
      end

      def schedule_webhooks(triggered_webhooks, options)
        triggered_webhooks.each_with_index do | triggered_webhook, i |
          logger.info "Scheduling job for webhook with uuid #{triggered_webhook.webhook.uuid}, context: #{triggered_webhook.event_context}"
          logger.debug "Schedule webhook with options #{options}"

          job_data = { triggered_webhook: triggered_webhook }.deep_merge(options)
          begin
            # Delay slightly to make sure the request transaction has finished before we execute the webhook
            Job.perform_in(5 + (i * 3), job_data)
          rescue StandardError => e
            logger.warn("Error scheduling webhook execution for webhook with uuid #{triggered_webhook&.webhook&.uuid}", e)
            nil
          end
        end
      end

      def create_triggered_webhooks_for_webhooks webhooks, pact, verification, event_name, event_context
        webhooks.flat_map do | webhook |
          expanded_event_contexts = expand_events_for_currently_deployed_environments(webhook, pact, event_context)
          expanded_event_contexts = expand_events_for_required_verifications(event_name, pact, expanded_event_contexts)
          expanded_event_contexts = expanded_event_contexts.flat_map { | ec | expand_events_for_verification_of_multiple_selected_pacts(ec) }

          expanded_event_contexts.collect do | expanded_event_context |
            pact_for_triggered_webhook = verification ? find_pact_for_verification_triggered_webhook(pact, expanded_event_context) : pact
            webhook_repository.create_triggered_webhook(next_uuid, webhook, pact_for_triggered_webhook, verification, RESOURCE_CREATION, event_name, expanded_event_context)
          end
        end
      end
      private :create_triggered_webhooks_for_webhooks

      def merge_consumer_version_selectors(consumer_version_number, selectors, event_context)
        event_context.merge(
          consumer_version_number: consumer_version_number,
          consumer_version_tags: selectors.collect{ | selector | selector[:tag] }.compact.uniq
        )
      end
      private :merge_consumer_version_selectors

      # Now that we de-duplicate the pact contents when verifying though the 'pacts for verification' API,
      # we no longer get a webhook triggered for the verification results publication of each indiviual
      # consumer version tag, meaning that only the most recent commit will get the updated verification status.
      # To fix this, each URL of the pacts returned by the 'pacts for verification' API now contains the
      # relevant selectors in the metadata, so that when the verification results are published,
      # we can parse that metadata, and trigger one webhook for each consumer version like we used to.
      # Actually, we used to trigger one webhook per tag, but given that the most likely use of the
      # verification published webhook is for reporting git statuses,
      # it makes more sense to trigger per consumer version number (ie. commit).
      def expand_events_for_verification_of_multiple_selected_pacts(event_context)
        if event_context[:consumer_version_selectors].is_a?(Array)
          event_context[:consumer_version_selectors]
            .group_by{ | selector | selector[:consumer_version_number] }
            .collect { | consumer_version_number, selectors | merge_consumer_version_selectors(consumer_version_number, selectors, event_context.without(:consumer_version_selectors)) }
        else
          [event_context]
        end
      end
      private :expand_events_for_verification_of_multiple_selected_pacts

      def expand_events_for_currently_deployed_environments(webhook, pact, event_context)
        if PactBroker.feature_enabled?(:expand_currently_deployed_provider_versions) && webhook.expand_currently_deployed_provider_versions?
          deployed_version_service.find_currently_deployed_versions_for_pacticipant(pact.provider).collect(&:version_number).uniq.collect do | version_number |
            event_context.merge(currently_deployed_provider_version_number: version_number)
          end
        else
          [event_context]
        end
      end
      private :expand_events_for_currently_deployed_environments

      def find_pact_for_verification_triggered_webhook(pact, reconstituted_event_context)
        if reconstituted_event_context[:consumer_version_number]
          find_pact_params = {
            consumer_name: pact.consumer_name,
            provider_name: pact.provider_name,
            consumer_version_number: reconstituted_event_context[:consumer_version_number]
          }
          pact_service.find_pact(find_pact_params) || pact
        else
          pact
        end
      end
      private :find_pact_for_verification_triggered_webhook

      def expand_events_for_required_verifications(event_name, pact, event_contexts)
        if event_name == PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED
          required_verifications = verification_service.calculate_required_verifications_for_pact(pact)
          event_contexts.flat_map do | event_context |
            required_verifications.collect do | required_verification |
              event_context.merge(provider_version_number: required_verification.provider_version.number, provider_version_descriptions: required_verification.provider_version_descriptions.uniq)
            end
          end
        else
          event_contexts
        end
      end
    end
  end
end
