require 'pact/consumer_contract'
require 'pact/consumer/interactions_filter'
require 'pact/consumer_contract/file_name'
require 'pact/consumer_contract/pact_file'
require 'pact/consumer_contract/consumer_contract_decorator'
require 'pact/shared/active_support_support'
require 'fileutils'

module Pact

  class ConsumerContractWriterError < StandardError; end

  class ConsumerContractWriter

    include Pact::FileName
    include Pact::PactFile
    include ActiveSupportSupport

    def initialize consumer_contract_details, logger
      @logger = logger
      @consumer_contract_details = consumer_contract_details
      @pactfile_write_mode = consumer_contract_details.fetch(:pactfile_write_mode, :overwrite).to_sym
      @interactions = consumer_contract_details.fetch(:interactions)
    end

    def consumer_contract
      @consumer_contract ||= Pact::ConsumerContract.new(
        consumer: ServiceConsumer.new(name: consumer_contract_details[:consumer][:name]),
        provider: ServiceProvider.new(name: consumer_contract_details[:provider][:name]),
        interactions: interactions_for_new_consumer_contract)
    end

    def write
      update_pactfile
      pact_json
    end

    private

    attr_reader :consumer_contract_details, :pactfile_write_mode, :interactions, :logger

    def pactfile_path
      raise 'You must specify a consumer and provider name' unless (consumer_name && provider_name)
      file_path consumer_name, provider_name, pact_dir
    end

    def update_pactfile
      logger.debug "Updating pact file for #{provider_name} at #{pactfile_path}"
      FileUtils.mkdir_p File.dirname(pactfile_path)
      File.open(pactfile_path, 'w') do |f|
        f.write pact_json
      end
    end

    def pact_json
      @pact_json ||= fix_json_formatting(JSON.pretty_generate(decorated_pact))
    end

    def decorated_pact
      @decorated_pact ||= Pact::ConsumerContractDecorator.new(consumer_contract)
    end

    def interactions_for_new_consumer_contract
      if pactfile_write_mode == :update
        merged_interactions = existing_interactions.dup
        filter = Consumer::UpdatableInteractionsFilter.new(merged_interactions)
        interactions.each {|i| filter << i }
        merged_interactions
      else
        interactions
      end
    end

    def existing_interactions
      interactions = []
      if pactfile_exists?
        begin
          interactions = existing_consumer_contract.interactions
          info_and_puts "*****************************************************************************"
          info_and_puts "Updating existing file .#{pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
          info_and_puts "Only interactions defined in this test run will be updated."
          info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
          info_and_puts "*****************************************************************************"
        rescue StandardError => e
          warn_and_stderr "Could not load existing consumer contract from #{pactfile_path} due to #{e}"
          logger.error e
          logger.error e.backtrace
          warn_and_stderr "Creating a new file."
        end
      end
      interactions
    end

    def pactfile_exists?
      File.exist?(pactfile_path)
    end

    def existing_consumer_contract
      Pact::ConsumerContract.from_uri(pactfile_path)
    end

    def warn_and_stderr msg
      Pact.configuration.error_stream.puts msg
      logger.warn msg
    end

    def info_and_puts msg
      $stdout.puts msg
      logger.info msg
    end

    def consumer_name
      consumer_contract_details[:consumer][:name]
    end

    def provider_name
      consumer_contract_details[:provider][:name]
    end

    def pact_dir
      unless consumer_contract_details[:pact_dir]
        raise ConsumerContractWriterError.new("Please indicate the directory to write the pact to by specifying the pact_dir field")
      end
      consumer_contract_details[:pact_dir]
    end
  end

end