require "pact_broker/client/hash_refinements"

module PactBroker
  module Client
    module CLI
      # Thor::Error will have its backtrace hidden
      class PactPublicationError < ::Thor::Error; end

      module PactCommands
        using PactBroker::Client::HashRefinements

        def self.included(thor)
          thor.class_eval do
            desc 'publish PACT_DIRS_OR_FILES ...', "Publish pacts to a Pact Broker."
            method_option :consumer_app_version, required: true, aliases: "-a", desc: "The consumer application version"
            method_option :branch, aliases: "-h", desc: "Repository branch of the consumer version"
            method_option :auto_detect_version_properties, type: :boolean, default: false, desc: "Automatically detect the repository branch from known CI environment variables or git CLI. Supports Buildkite, Circle CI, Travis CI, GitHub Actions, Jenkins, Hudson, AppVeyor, GitLab, CodeShip, Bitbucket and Azure DevOps."
            method_option :tag, aliases: "-t", type: :array, banner: "TAG", desc: "Tag name for consumer version. Can be specified multiple times."
            method_option :tag_with_git_branch, aliases: "-g", type: :boolean, default: false, required: false, desc: "Tag consumer version with the name of the current git branch. Supports Buildkite, Circle CI, Travis CI, GitHub Actions, Jenkins, Hudson, AppVeyor, GitLab, CodeShip, Bitbucket and Azure DevOps. Default: false"
            method_option :build_url, desc: "The build URL that created the pact"
            method_option :merge, type: :boolean, default: false, require: false, desc: "If a pact already exists for this consumer version and provider, merge the contents. Useful when running Pact tests concurrently on different build nodes."
            output_option_json_or_text
            shared_authentication_options

            def publish(*pact_files)
              require 'pact_broker/client/error'
              validate_credentials
              validate_pact_files(pact_files)
              result = publish_pacts(pact_files)
              $stdout.puts result.message
              exit(1) unless result.success
            rescue PactBroker::Client::Error => e
              raise PactPublicationError, "#{e.class} - #{e.message}"
            end

            desc 'list-latest-pact-versions', 'List the latest pact for each integration'
            shared_authentication_options
            output_option_json_or_table
            def list_latest_pact_versions(*required_but_ignored)
              require 'pact_broker/client/pacts/list_latest_versions'
              result = PactBroker::Client::Pacts::ListLatestVersions.call(options.broker_base_url, options.output, pact_broker_client_options)
              $stdout.puts result.message
              exit(1) unless result.success
            end

            no_commands do
              def validate_pact_files pact_files
                unless pact_files && pact_files.any?
                  raise ::Thor::RequiredArgumentMissingError, "No value provided for required pact_files"
                end
              end


              def publish_pacts pact_files
                require 'pact_broker/client/publish_pacts'

                write_options = options[:merge] ? { write: :merge } : {}
                consumer_version_params = {
                  number: options.consumer_app_version,
                  branch: branch,
                  tags: tags,
                  build_url: options.build_url,
                  version_required: (!!options.branch || !!options.build_url || explict_auto_detect_version_properties)
                }.compact

                PactBroker::Client::PublishPacts.call(
                  options.broker_base_url,
                  file_list(pact_files),
                  consumer_version_params,
                  { merge: options[:merge], output: options.output }.compact,
                  pact_broker_client_options.merge(write_options)
                )
              end

              def file_list pact_files
                require 'rake/file_list'

                correctly_separated_pact_files = pact_files.collect{ |path| path.gsub(/\\+/, '/') }
                paths = Rake::FileList[correctly_separated_pact_files].collect do | path |
                  if File.directory?(path)
                    Rake::FileList[File.join(path, "*.json")]
                  else
                    path
                  end
                end.flatten
                validate_pact_path_list(paths)
              end

              def validate_pact_path_list(paths)
                paths.collect do | path |
                  if File.exist?(path)
                    path
                  elsif path.start_with?("-")
                    raise Thor::Error.new("ERROR: pact-broker publish was called with invalid arguments #{[path]}")
                  else
                    raise Thor::Error.new("Specified pact file '#{path}' does not exist. This sometimes indicates one of the arguments has been specified with the wrong name and has been incorrectly identified as a file path.")
                  end
                end
              end

              def tags
                require 'pact_broker/client/git'

                t = [*options.tag]
                t << PactBroker::Client::Git.branch(raise_error: true) if options.tag_with_git_branch
                t.compact.uniq
              end

              def branch
                require 'pact_broker/client/git'

                if options.branch.nil? && options.auto_detect_version_properties
                  PactBroker::Client::Git.branch(raise_error: explict_auto_detect_version_properties)
                else
                  options.branch
                end
              end

              def explict_auto_detect_version_properties
                @explict_auto_detect_version_properties ||= ARGV.include?("--auto-detect-version-properties")
              end
            end
          end
        end
      end
    end
  end
end