# frozen_string_literal: true

require_relative "gemfile_filter"
require_relative "gemspec_filter"
require_relative "dependency"
require_relative "dependency_set"

module KeepUp
  # A Gemfile with its current set of locked dependencies.
  class Bundle
    OUTDATED_MATCHER =
      /([^ ]*) \(newest ([^,]*), installed ([^,]*)(?:, requested (.*))?\)/.freeze
    UPDATE_MATCHER =
      /(?:Using|Installing|Fetching) ([^ ]*) ([^ ]*)(?: \(was (.*)\))?/.freeze

    def initialize(runner:, local:)
      @runner = runner
      @local = local
    end

    def dependencies
      @dependencies ||=
        begin
          command = "bundle outdated --parseable#{" --local" if @local}"
          lines = run_filtered command, OUTDATED_MATCHER
          lines.map do |name, newest, version, requirement|
            build_dependency(name, newest, version, requirement)
          end
        end
    end

    def dependency_set
      @dependency_set ||= DependencySet.new(dependencies)
    end

    def check?
      _, status = @runner.run2 "bundle check"
      status == 0
    end

    def update_gemfile_contents(update)
      update = dependency_set.find_specification_update(update)
      return unless update

      update if GemfileFilter.apply_to_file("Gemfile", update)
    end

    def update_gemspec_contents(update)
      return unless gemspec_name

      update = dependency_set.find_specification_update(update)
      return unless update

      update if GemspecFilter.apply_to_file(gemspec_name, update)
    end

    # Update lockfile and return resulting spec, or false in case of failure
    def update_lockfile(update, old_version)
      update_name = update.name
      command = "bundle update#{" --local" if @local} --conservative #{update_name}"
      lines = run_filtered command, UPDATE_MATCHER
      lines.each do |name, version, _old_version|
        next unless name == update_name && old_version

        current = Gem::Specification.new(name, old_version)
        result = Gem::Specification.new(name, version)
        return result if result.version > current.version
      end
      nil
    end

    private

    def gemspec
      @gemspec ||=
        if gemspec_name
          gemspec_path = File.expand_path(gemspec_name)
          eval File.read(gemspec_name), nil, gemspec_path
        end
    end

    def gemspec_dependencies
      @gemspec_dependencies ||=
        if gemspec
          gemspec.dependencies
        else
          []
        end
    end

    def build_dependency(name, newest, version, requirement)
      requirement_list = requirement&.split(/,\s*/)
      requirement_list ||= fetch_gemspec_dependency_requirements(name)
      version = version.split.first
      newest = newest.split.first
      Dependency.new(
        name: name,
        locked_version: version,
        newest_version: newest,
        requirement_list: requirement_list
      )
    end

    def fetch_gemspec_dependency_requirements(name)
      dep = gemspec_dependencies.find { |it| it.name == name }
      return unless dep

      dep.requirements_list
    end

    def gemspec_name
      @gemspec_name ||= Dir.glob("*.gemspec").first
    end

    def run_filtered(command, regexp)
      result = @runner.run command
      lines = result.split("\n").reject(&:empty?)
      lines.filter_map do |line|
        matchdata = regexp.match line
        next unless matchdata

        matchdata.to_a[1..]
      end
    end
  end
end