# frozen_string_literal: true

require 'git'
require_relative '../data/version'

module GFSM
  module Tools

    class VersionBumperSettings
      attr_reader :repository, :configuration, :force, :force_version, :prerelease, :prerelease_name, :from, :to, :initial_version

      def initialize(cli_args)
        @repository = GFSM::Tools::GitUtilities.load_repo(get_repository_path(cli_args))
        @configuration = GFSM::Data::Configuration.new(get_configuration_file_path(cli_args))
        @force = get_force(cli_args)
        @force_version = get_force_version(cli_args)
        @prerelease = get_prerelease(cli_args)
        @prerelease_name = get_prerelease_name(cli_args)
        @from = get_from(cli_args)
        @to = get_to(cli_args)
        @initial_version = get_initial_version(cli_args)
        @from = GFSM::Tools::GitUtilities.extract_last_tag_name(@repository) if @from.nil?
      end

      def self.cli_info
        options = <<~OPTIONS
          --force                               # When there are no commits with a changelog trailer the version won't get bumped. Use this flag to force the version bump. Unless specified, it will increase the patch
          --force-version <major|minor|match>   # What section of the version to bump if the changelog trailer is not found.
          --prerelease                          # Use this switch to also include a prerelease in the version. By default will add 'pre' and increment it like 'pre.1', 'pre.2' and so on
          --prerelease-name <name>              # Name of the prerelease that will get added when the switch is enabled
          --configuration <path>                # Path to the configuration YAML file
          --from <hash or tag>                  # The commit hash or tag to start from when extracting the commits. By default will use the latest tag
          --to <hash, tag or HEAD>              # The commit hash or tag to stop at when extracting the commits. By default will use HEAD to get to the latest commit on the current branch
          --configuration <path>                # Path to the configuration YAML file
          --path <path>                         # Path to the repository. By default will use the current directory
          --initial-version <x.y.z>             # Version to use when no existing version is found, useful when initializing the first one
        OPTIONS

        environment_variables = <<~ENVVARS
          FORCE_BUMP                            # Equivalent to --force
          FORCE_BUMP_VERSION                    # Equivalent to --force-version
          PRERELEASE                            # Equivalent to --prerelease
          PRERELEASE_NAME                       # Equivalent to --prerelease-name
          CONFIGURATION_FILE                    # Equivalent to --configuration
          REPOSITORY_PATH                       # Equivalent to --path
          FROM_COMMIT                           # Equivalent to --from
          TO_COMMIT                             # Equivalent to --to
          INITIAL_VERSION                       # Equivalent to --initial-version
        ENVVARS

        return {
          usage: "[--force] [--force-version <major|minor|patch>] [--configuration <configuration_file_path>] [--prerelease] [--prerelease-name <prerelease_name>] [--from <hash or tag>] [--to <hash, tag or HEAD>] [--path <repository_path] [--initial-version <x.y.z>]",
          options: options,
          environment_variables: environment_variables
        }
      end

      private

      def extract_switch_value(args, switch, default_value, env_name = nil)
        return ENV.fetch(env_name) if ENV.has_key?(env_name)

        switch_index = args.find_index(switch)
        
        return default_value unless switch_index &&
                                    (switch_index + 1) < args.length

        args[switch_index + 1]
      end

      def extract_boolean_value(args, switch, default_value, env_name = nil)
        if ENV.has_key?(env_name)
          return ENV.fetch(env_name) == "true"
        end

        switch_index = args.find_index(switch)
        
        return default_value unless switch_index
        
        true
      end
      
      def get_force(args)
        extract_boolean_value(args, "--force", false, "FORCE_BUMP")
      end

      def get_configuration_file_path(args)
        extract_switch_value(args, "--configuration", "./gfsmrc.yml", "CONFIGURATION_FILE")
      end
      
      def get_prerelease(args)
        extract_boolean_value(args, "--prerelease", false, "PRERELEASE")
      end

      def get_prerelease_name(args)
        extract_switch_value(args, "--prerelease-name", "pre", "PRERELEASE_NAME")
      end
      
      def get_repository_path(args)
        extract_switch_value(args, "--path", ".", "REPOSITORY_PATH")
      end
      
      def get_force_version(args)
        extract_switch_value(args, "--force-version", "patch", "FORCE_BUMP_VERSION")
      end
      
      def get_from(args)
        extract_switch_value(args, "--from", nil, "FROM_COMMIT")
      end
      
      def get_to(args)
        extract_switch_value(args, "--to", "HEAD", "TO_COMMIT")
      end
      
      def get_initial_version(args)
        extract_switch_value(args, "--initial-version", "0.0.0", "INITIAL_VERSION")
      end
    end

    class VersionBumper
      attr_reader :version, :subdivisions

      def initialize(settings)
        @settings = settings
      end

      def execute
        changelog_commits = GFSM::Tools::GitUtilities.extract_commits_with_changelog_trailer(@settings.repository, @settings.from, @settings.to)

        @version = GFSM::Tools::CurrentVersionLoader.load_current_version(@settings.repository, @settings.initial_version)
        @subdivisions = GFSM::Tools::CommitsSubdivider.subdivide_commits(@settings.configuration, changelog_commits)

        if !@subdivisions || @subdivisions.empty?
          if @settings.force
            @version.bump!(@settings.force_version == "major", @settings.force_version == "minor", @settings.force_version == "patch", @settings.prerelease, @settings.prerelease_name)
          end
        else
          highest_bump = GFSM::Data::ChangeType.find_highest_bump(@subdivisions.keys)
          @version.bump!(highest_bump == :major, highest_bump == :minor, highest_bump == :patch, @settings.prerelease, @settings.prerelease_name)
        end

        return @version
      end
    end
  end
end