# frozen_string_literal: true
module API
  class Integrations < ::API::Base
    feature_category :integrations

    integrations = Helpers::IntegrationsHelpers.integrations
    integration_classes = Helpers::IntegrationsHelpers.integration_classes

    if Rails.env.development?
      integrations['mock-ci'] = [
        {
          required: true,
          name: :mock_service_url,
          type: String,
          desc: 'URL to the mock integration'
        }
      ]
      integrations['mock-deployment'] = []
      integrations['mock-monitoring'] = []

      integration_classes += Helpers::IntegrationsHelpers.development_integration_classes
    end

    INTEGRATIONS = integrations.freeze

    integration_classes.each do |integration|
      event_names = integration.try(:event_names) || next
      event_names.each do |event_name|
        INTEGRATIONS[integration.to_param.tr("_", "-")] << {
          required: false,
          name: event_name.to_sym,
          type: String,
          desc: IntegrationsHelper.integration_event_description(integration, event_name)
        }
      end
    end

    TRIGGER_INTEGRATIONS = {
      'mattermost-slash-commands' => [
        {
          name: :token,
          type: String,
          desc: 'The Mattermost token'
        }
      ],
      'slack-slash-commands' => [
        {
          name: :token,
          type: String,
          desc: 'The Slack token'
        }
      ]
    }.freeze

    helpers do
      def integration_attributes(integration)
        integration.fields.inject([]) do |arr, hash|
          arr << hash[:name].to_sym
        end
      end
    end

    # The API officially documents only the `:id/integrations` API paths.
    # We support the older `id:/services` path for backwards-compatibility in API V4.
    # The support for `:id/services` can be dropped if we create an API V5.
    [':id/services', ':id/integrations'].each do |path|
      params do
        requires :id, type: String, desc: 'The ID of a project'
      end
      resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
        before { authenticate! }
        before { authorize_admin_project }

        desc 'Get all active project integrations' do
          success Entities::ProjectIntegrationBasic
        end
        get path do
          integrations = user_project.integrations.active

          present integrations, with: Entities::ProjectIntegrationBasic
        end

        INTEGRATIONS.each do |slug, settings|
          desc "Set #{slug} integration for project"
          params do
            settings.each do |setting|
              if setting[:required]
                requires setting[:name], type: setting[:type], desc: setting[:desc]
              else
                optional setting[:name], type: setting[:type], desc: setting[:desc]
              end
            end
          end
          put "#{path}/#{slug}" do
            integration = user_project.find_or_initialize_integration(slug.underscore)
            params = declared_params(include_missing: false).merge(active: true)

            if integration.update(params)
              present integration, with: Entities::ProjectIntegration
            else
              render_api_error!('400 Bad Request', 400)
            end
          end
        end

        desc "Delete an integration from a project"
        params do
          requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
        end
        delete "#{path}/:slug" do
          integration = user_project.find_or_initialize_integration(params[:slug].underscore)

          destroy_conditionally!(integration) do
            attrs = integration_attributes(integration).index_with { nil }.merge(active: false)

            render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
          end
        end

        desc 'Get the integration settings for a project' do
          success Entities::ProjectIntegration
        end
        params do
          requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
        end
        get "#{path}/:slug" do
          integration = user_project.find_or_initialize_integration(params[:slug].underscore)

          not_found!('Integration') unless integration&.persisted?

          present integration, with: Entities::ProjectIntegration
        end
      end

      TRIGGER_INTEGRATIONS.each do |integration_slug, settings|
        helpers do
          def slash_command_integration(project, integration_slug, params)
            project.integrations.active.find do |integration|
              integration.try(:token) == params[:token] && integration.to_param == integration_slug.underscore
            end
          end
        end

        params do
          requires :id, type: String, desc: 'The ID of a project'
        end
        resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
          desc "Trigger a slash command for #{integration_slug}" do
            detail 'Added in GitLab 8.13'
          end
          params do
            settings.each do |setting|
              requires setting[:name], type: setting[:type], desc: setting[:desc]
            end
          end
          post "#{path}/#{integration_slug.underscore}/trigger", urgency: :low do
            project = find_project(params[:id])

            # This is not accurate, but done to prevent leakage of the project names
            not_found!('Integration') unless project

            integration = slash_command_integration(project, integration_slug, params)
            result = integration.try(:trigger, params)

            if result
              status result[:status] || 200
              present result
            else
              not_found!('Integration')
            end
          end
        end
      end
    end
  end
end

API::Integrations.prepend_mod_with('API::Integrations')
