require "pact_broker/json"
require "pact_broker/pacts/content"

=begin
This class most accurately represents a PactPublication
=end

module PactBroker
  class UnsetAttributeError < StandardError; end
  class UnsetAttribute; end

  module Domain
    class Pact

      # The ID is the pact_publication ID
      attr_accessor :id,
        :provider,
        :consumer_version,
        :created_at,
        :json_content,
        :consumer_version_number,
        :revision_number,
        :pact_version_sha,
        :head_tag_names

      attr_writer :consumer,
                  :latest_verification,
                  :consumer_version_tag_names,
                  :consumer_version_branch_names

      def initialize attributes = {}
        @latest_verification = UnsetAttribute.new
        @consumer_version_tag_names = UnsetAttribute.new
        @consumer_version_branch_names = UnsetAttribute.new
        attributes.each_pair do | key, value |
          self.send(key.to_s + "=", value)
        end
      end

      def consumer_name
        consumer.name
      end

      def provider_name
        provider.name
      end

      def consumer
        @consumer || consumer_version.pacticipant
      end

      def consumer_version_tag_names
        get_attribute_if_set :consumer_version_tag_names
      end

      def consumer_version_branch_names
        get_attribute_if_set :consumer_version_branch_names
      end

      def latest_verification
        get_attribute_if_set :latest_verification
      end

      def to_s
        "Pact: consumer=#{consumer.name} provider=#{provider.name}"
      end

      def to_json _options = {}
        json_content
      end

      def name
        "Pact between #{consumer.name} (#{consumer_version_number}) and #{provider.name}"
      end

      def version_and_updated_date
        "Version #{consumer_version_number} - #{created_at.to_time.localtime.strftime("%d/%m/%Y")}"
      end

      def content_hash
        JSON.parse(json_content, PACT_PARSING_OPTIONS)
      end

      def content_object
        @content_object ||= begin
          PactBroker::Pacts::Content.from_json(json_content)
        rescue
          PactBroker::Pacts::Content.new
        end
      end

      def pact_publication_id
        id
      end

      def select_pending_provider_version_tags(provider_version_tags)
        tags_with_successful_verifications_from_that_branch = db_model.pact_version.select_provider_tags_with_successful_verifications(provider_version_tags)
        tags_with_previous_successful_verifications_from_other_branches = db_model.pact_version.select_provider_tags_with_successful_verifications_from_another_branch_from_before_this_branch_created(provider_version_tags)
        provider_version_tags - tags_with_successful_verifications_from_that_branch - tags_with_previous_successful_verifications_from_other_branches
      end

      # @param [String] branch_name the name of the provider branch that will be verifying the pacts
      # @return Boolean whether or not the pact is in pending state (ie. the build should not fail if the verification fails)
      def pending_for_provider_branch?(branch_name)
        pact_version.pending_for_provider_branch?(branch_name)
      end

      def pending_for_any_provider_branch?
        !pact_version.verified_successfully_by_any_provider_version?
      end

      def <=> other
        self_fields = [consumer_name.downcase, provider_name.downcase, consumer_version.order || 0]
        other_fields = [other.consumer_name.downcase, other.provider_name.downcase, other.consumer_version.order || 0]
        self_fields <=> other_fields
      end

      private

      attr_accessor :db_model

      # Really not sure about mixing Sequel model class into this PORO...
      # But it's much nicer than using a repository to find out the pending information :(
      def pact_version
        db_model.pact_version
      end

      # This class has various incarnations with different properties loaded.
      # They should probably be different classes, but for now, raise an error if
      # an attribute is called when it hasn't been set in the constuctor, because
      # returning nil when there should be an object causes bugs.
      def get_attribute_if_set attribute_name
        val = instance_variable_get("@#{attribute_name}".to_sym)
        if val.is_a?(UnsetAttribute)
          raise UnsetAttributeError.new("Attribute #{attribute_name} not set")
        else
          val
        end
      end
    end
  end
end
