require "pact_broker/api/resources/base_resource"
require "pact_broker/api/resources/webhook_execution_methods"
require "pact_broker/contracts/contracts_to_publish"
require "pact_broker/api/contracts/publish_contracts_schema"
require "pact_broker/pacts/parse"

module PactBroker
  module Api
    module Resources
      class PublishContracts < BaseResource
        include WebhookExecutionMethods

        def content_types_provided
          [["application/hal+json"]]
        end

        def allowed_methods
          ["POST", "OPTIONS"]
        end

        def malformed_request?
          super || (request.post? && content_type_json? && validation_errors_for_schema?)
        end

        def process_post
          if content_type_json?
            if conflict_notices.any?
              set_conflict_response
              409
            else
              publish_contracts
              true
            end
          else
            415
          end
        end

        def policy_name
          :'contracts::contracts'
        end

        # for pactflow
        def policy_record
          @policy_record ||= pacticipant_service.find_pacticipant_by_name(parsed_contracts.pacticipant_name)
        end

        # for pactflow
        def policy_record_context
          { pacticipant: policy_record }
        end

        private

        def parsed_contracts
          @parsed_contracts ||= decorator_class(:publish_contracts_decorator).new(PactBroker::Contracts::ContractsToPublish.new).from_hash(params, { user_options: { sha_generator: PactBroker.configuration.sha_generator } } )
        end

        def params
          p = super(default: {}, symbolize_names: false)
          if p["contracts"].is_a?(Array)
            p["contracts"].each do | contract |
              if contract.is_a?(Hash)
                decode_and_parse_content(contract)
              end
            end
          end
          p
        end

        def schema
          api_contract_class(:publish_contracts_schema)
        end

        def decode_and_parse_content(contract)
          contract["decodedContent"] = base64_decode(contract["content"])
          if contract["decodedContent"]
            if contract["contentType"]&.include?("json")
              contract["decodedParsedContent"] = parse_json(contract["decodedContent"])
            elsif contract["contentType"]&.include?("yml")
              contract["decodedParsedContent"] = parse_yaml(contract["decodedContent"])
            end
          end
        end

        def publish_contracts
          handle_webhook_events(consumer_version_branch: parsed_contracts.branch, build_url: parsed_contracts.build_url) do
            results = contract_service.publish(parsed_contracts, base_url: base_url)
            response.body = decorator_class(:publish_contracts_results_decorator).new(results).to_json(**decorator_options)
          end
        end

        def set_conflict_response
          response.body = {
            notices: conflict_notices.collect(&:to_h),
            errors: {
              contracts: conflict_notices.select(&:error?).collect(&:text)
            }
          }.to_json
          response.headers["content-type"] = "application/json;charset=utf-8"
        end

        def conflict_notices
          @conflict_notices ||= contract_service.conflict_notices(parsed_contracts, base_url: base_url)
        end

        def base64_decode(content)
          Base64.strict_decode64(content) rescue nil
        end

        # TODO put this somewhere shareable
        def parse_yaml(content)
          YAML.safe_load(content, permitted_classes: [Time, Date, DateTime]) rescue nil
        end

        def parse_json(content)
          PactBroker::Pacts::Parse.call(content) rescue nil
        end
      end
    end
  end
end
