require 'bundler'
require_relative 'gemfile_filter'
require_relative 'gemspec_filter'
require_relative 'dependency'

module KeepUp
  # A Gemfile with its current set of locked dependencies.
  class Bundle
    def direct_dependencies
      (gemspec_dependencies + gemfile_dependencies).map do |dep|
        spec = locked_spec dep
        next unless spec
        Dependency.new(name: dep.name,
                       version: dep.requirements_list.first,
                       locked_version: spec.version)
      end.compact
    end

    def apply_updated_dependency(dependency)
      puts "Updating #{dependency.name} to #{dependency.version}"
      update_gemfile_contents(dependency)
      update_gemspec_contents(dependency)
      update_lockfile(dependency)
    end

    private

    def gemfile_dependencies
      bundler_lockfile.dependencies
    end

    def gemspec_dependencies
      gemspec_source = bundler_lockfile.sources.find { |it| it.is_a? Bundler::Source::Gemspec }
      return [] unless gemspec_source
      gemspec_source.gemspec.dependencies
    end

    def locked_spec(dep)
      bundler_lockfile.specs.find { |it| it.name == dep.name }
    end

    def bundler_lockfile
      @bundler_lockfile ||= bundler_definition.locked_gems
    end

    def bundler_definition
      @bundler_definition ||= Bundler::Definition.build('Gemfile', 'Gemfile.lock', false)
    end

    def update_gemfile_contents(dependency)
      current_dependency = gemfile_dependencies.find { |it| it.name == dependency.name }
      return if !current_dependency
      return if current_dependency.matches_spec?(dependency)
      contents = File.read 'Gemfile'
      updated_contents = GemfileFilter.apply(contents, dependency)
      File.write 'Gemfile', updated_contents
    end

    def update_gemspec_contents(dependency)
      current_dependency = gemspec_dependencies.find { |it| it.name == dependency.name }
      return if !current_dependency
      return if current_dependency.matches_spec?(dependency)
      contents = File.read gemspec_name
      updated_contents = GemspecFilter.apply(contents, dependency)
      File.write gemspec_name, updated_contents
    end

    def gemspec_name
      @gemspec_name ||= begin
                          gemspecs = Dir.glob('*.gemspec')
                          case gemspecs.count
                          when 1
                            gemspecs.first
                          else
                            raise '???'
                          end
                        end
    end

    def update_lockfile(dependency)
      Bundler.clear_gemspec_cache
      Bundler::Definition.build('Gemfile', 'Gemfile.lock',
                                gems: [dependency.name]).lock('Gemfile.lock')
      true
    rescue Bundler::VersionConflict
      puts 'Update failed'
      false
    end
  end
end