require "sequel"
require "pact_broker/domain/webhook"
require "pact_broker/webhooks/webhook_request_template"
require "pact_broker/domain/pacticipant"

module PactBroker
  module Webhooks
    class Webhook < Sequel::Model
      set_primary_key :id
      plugin :serialization, :json, :headers
      plugin :timestamps, update_on_create: true

      associate(:many_to_one, :provider, :class => "PactBroker::Domain::Pacticipant", :key => :provider_id, :primary_key => :id)
      associate(:many_to_one, :consumer, :class => "PactBroker::Domain::Pacticipant", :key => :consumer_id, :primary_key => :id)
      one_to_many :events, :class => "PactBroker::Webhooks::WebhookEvent", :reciprocal => :webhook

      dataset_module do
        include PactBroker::Repositories::Helpers

        # Keep the triggered webhooks after the webhook has been deleted
        def delete
          require "pact_broker/webhooks/triggered_webhook"
          TriggeredWebhook.where(webhook: self).update(webhook_id: nil)
          super
        end

        def for_event_name(event_name)
          join(:webhook_events, { webhook_id: :id })
            .where(Sequel[:webhook_events][:name] => event_name)
        end

        def find_by_consumer_and_or_provider consumer, provider
          where(
            Sequel.|(
              { consumer_id: consumer.id, provider_id: provider.id },
              { consumer_id: nil, provider_id: provider.id },
              { consumer_id: consumer.id, provider_id: nil },
              { consumer_id: nil, provider_id: nil}
            )
          )
        end

        def find_by_consumer_and_provider consumer, provider
          criteria = {
            consumer_id: (consumer ? consumer.id : nil),
            provider_id: (provider ? provider.id : nil)
          }
          where(criteria)
        end

        def enabled
          where(enabled: true)
        end
      end

      def update_from_domain webhook
        set(self.class.properties_hash_from_domain(webhook))
      end

      def self.from_domain webhook, consumer, provider
        new(
          properties_hash_from_domain(webhook).merge(uuid: webhook.uuid)
        ).tap do | db_webhook |
          db_webhook.consumer_id = consumer.id if consumer
          db_webhook.provider_id = provider.id if provider
        end
      end

      def self.not_plain_text_password password
        password.nil? ? nil : Base64.strict_encode64(password)
      end

      def to_domain
        Domain::Webhook.new(
          uuid: uuid,
          description: description,
          consumer: consumer,
          provider: provider,
          events: events,
          request: Webhooks::WebhookRequestTemplate.new(request_attributes),
          enabled: enabled,
          created_at: created_at,
          updated_at: updated_at)
      end

      def request_attributes
        values.merge(headers: headers, body: parsed_body, password: plain_text_password, uuid: uuid)
      end

      def plain_text_password
        password.nil? ? nil : Base64.strict_decode64(password)
      end

      def parsed_body
        if body && is_json_request_body
          JSON.parse(body)
        else
          body
        end
      end

      def is_for? integration
        (consumer_id == integration.consumer_id || !consumer_id) && (provider_id == integration.provider_id || !provider_id)
      end

      # Keep the triggered webhooks after the webhook has been deleted
      def delete
        require "pact_broker/webhooks/triggered_webhook"
        TriggeredWebhook.where(webhook_id: id).update(webhook_id: nil)
        super
      end


      def self.properties_hash_from_domain webhook
        is_json_request_body = !(String === webhook.request.body || webhook.request.body.nil?) # Can't rely on people to set content type
        {
          description: webhook.description,
          method: webhook.request.method,
          url: webhook.request.url,
          username: webhook.request.username,
          password: not_plain_text_password(webhook.request.password),
          enabled: webhook.enabled.nil? ? true : webhook.enabled,
          body: (is_json_request_body ? webhook.request.body.to_json : webhook.request.body),
          is_json_request_body: is_json_request_body,
          headers: webhook.request.headers
        }
      end
    end
  end
end

# Table: webhooks
# Columns:
#  id                   | integer                     | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
#  uuid                 | text                        | NOT NULL
#  method               | text                        | NOT NULL
#  url                  | text                        | NOT NULL
#  body                 | text                        |
#  is_json_request_body | boolean                     |
#  consumer_id          | integer                     |
#  provider_id          | integer                     |
#  created_at           | timestamp without time zone |
#  updated_at           | timestamp without time zone |
#  username             | text                        |
#  password             | text                        |
#  enabled              | boolean                     | DEFAULT true
#  description          | text                        |
#  headers              | text                        |
# Indexes:
#  webhooks_pkey   | PRIMARY KEY btree (id)
#  uq_webhook_uuid | UNIQUE btree (uuid)
# Foreign key constraints:
#  fk_webhooks_consumer | (consumer_id) REFERENCES pacticipants(id)
#  fk_webhooks_provider | (provider_id) REFERENCES pacticipants(id)
# Referenced By:
#  triggered_webhooks | triggered_webhooks_webhook_id_fkey | (webhook_id) REFERENCES webhooks(id)
#  webhook_events     | webhook_events_webhook_id_fkey     | (webhook_id) REFERENCES webhooks(id) ON DELETE CASCADE
#  webhook_executions | webhook_executions_webhook_id_fkey | (webhook_id) REFERENCES webhooks(id)
#  webhook_headers    | fk_webhookheaders_webhooks         | (webhook_id) REFERENCES webhooks(id)