#!/usr/bin/env ruby
# WANT_JSON

# init bundler in dev env
if ENV['QB_DEV_ENV']
  ENV.each {|k, v|
    if k.start_with? 'QB_DEV_ENV_'
      ENV[k.sub('QB_DEV_ENV_', '')] = v
    end
  }
  require 'bundler/setup'
end

require 'ostruct'

require 'qb'
require 'qb/package/version'
require 'cmds'
require 'pathname'
require 'uri'
require 'net/http'

class Result < OpenStruct
  def to_json *args
    to_h.to_json *args
  end
end

GITHUB_SSH_URL_RE = /^git@github\.com\:(?<owner>.*)\/(?<name>.*)\.git$/
GITHUB_HTTPS_URL_RE = /^https:\/\/github\.com\/(?<owner>.*)\/(?<name>.*)\.git$/

class PathFacts < QB::AnsibleModule
  # Add a bunch of useful things to know about the path
  def add_path_facts
    @result.expanded = @path.expand_path
    @result.exists = @path.exist?
    @result.is_expanded = @result.expanded == @path
    @result.is_absolute = @path.absolute?
    @result.is_relative = @path.relative?
    @result.is_dir = @path.directory?
    @result.is_file = @path.file?
    @result.is_cwd = @path == Pathname.getwd
    
    # Will raise if there is no relative path between them, in which case
    # 'relative' will be null.
    @result.relative = begin
      @path.relative_path_from Pathname.getwd
    rescue ArgumentError => error
    end
    
    # Pathname#realpath will raise if the path doesn't exist
    @result.realpath = begin
      @path.realpath
    rescue Exception => error
    end
    
    @result.is_realpath = @result.realpath == @path
  end
  
  # If the path is a Git repo (has a .git file in it's root) add useful
  # Git facts.
  def add_git_facts
    # see if we're in a git repo. first, we need a directory that exists
    dir = @path.expand_path.ascend.find {|p| p.directory? }
    
    Dir.chdir(dir) do
      root_result = Cmds.new "git rev-parse --show-toplevel"
      
      unless root_result.ok?
        @result.in_git_repo = false
        @result.is_git_root = false
        return
      end
      
      @result.in_git_repo = true
      
      git = @result.git = Result.new
      git.root = Pathname.new root_result.out.chomp
      @result.is_git_root = @path == git.root
      
      user = git.user = Result.new
      
      ['name', 'email'].each {|key|
        user[key] = begin
          Cmds.chomp! "git config user.#{ key }"
        rescue
        end
      }
      
      git.is_clean = Cmds.chomp!('git status --porcelain 2>/dev/null').empty?
      
      rev_parse = Cmds.capture 'git rev-parse HEAD'
      
      if rev_parse.ok?
        git.head = rev_parse.out.chomp
        git.head_short = git.head[0...7]
      else
        git.head = git.head_short = nil
      end
      
      branch = Cmds.capture 'git branch --no-color 2> /dev/null'
      
      git.branch = if branch.ok?
        if line = branch.out.lines.find {|line| line.start_with? '*'}
          if m = line.match(/\*\s+(\S+)/)
            m[1]
          end
        end
      end
      
      git.origin = begin
        Cmds.chomp! "git remote get-url origin"
      rescue
      end
      
      match = GITHUB_SSH_URL_RE.match(git.origin) ||
              GITHUB_HTTPS_URL_RE.match(git.origin)
      
      git.is_github = !! match
      
      return unless match
      
      git.owner = match['owner']
      git.name = match['name']
      git.full_name = "#{ git.owner }/#{ git.name }"
      
      if true == @args['github_api']
        github = git.github = Result.new
        github.api_url = "https://api.github.com/repos/#{ git.owner }/#{ git.name }"
        
        response = Net::HTTP.get_response URI(github.api_url)
        
        if response.is_a? Net::HTTPSuccess
          # parse response body and add everything to github result
          parsed = JSON.parse response.body
          parsed.each {|k, v| github[k] = v}
        else
          # assume it's private if we failed to find it
          github.private = true
        end
        
      end
    end
  end
  
  # Find the only *.gemspec path in the `@path` directory. Warns and returns
  # `nil` if there is more than one match.
  def gemspec_path
    paths = Pathname.glob(@path.join('*.gemspec'))
    
    case paths.length
    when 0
      nil
    when 1
      paths[0]
    else
      warn "found multiple gemspecs: #{ paths }, unable to pick one."
      nil
    end
  end
  
  # If `path` is a directory containing the source for a Ruby Gem, add 
  # useful information about it.
  def add_gem_facts
    unless @path.directory?
      @result.is_gem = false
      return
    end
    
    path = gemspec_path
    
    if path.nil?
      @result.is_gem = false
      return
    end
    
    @result.is_gem = true
    @result.package.types << 'gem'
    
    gem = @result.gem = Result.new
    
    gem.gemspec_path = gemspec_path.to_s
    
    spec = Gem::Specification::load(gemspec_path.to_s)
    gem.name = spec.name
    gem.version = QB::Package::Version.from_gem_version spec.version
  end
  
  # Add facts about an NPM package based in `@path`, if any.
  def add_npm_facts
    package_json_path = @path.join('package.json')
    
    unless @path.directory? && package_json_path.file?
      @result.is_npm = false
      return
    end
    
    @result.is_npm = true
    @result.package.types << 'npm'
    
    npm = @result.npm = Result.new
    
    npm.package_json = JSON.load package_json_path.read
    
    # To stay consistent with Gem
    npm.name = npm.package_json['name']
    
    if npm.package_json['version']
      npm.version = QB::Package::Version.from_npm_version(
        npm.package_json['version']
      )
    end
  end
  
  # Run the module.
  def main
    # check the 'path' arg
    unless @args['path'].is_a? String
      raise ArgumentError,
        "'path' arg must be a string, found #{ @args['path'].inspect }."
    end
    
    # We'll return the value of @result
    @result = Result.new
    
    # Default to signaling no change (we're not gonna change anything in this
    # module either)
    @result.changed = false
    
    # String warnings that will be shown to the user
    @result.warnings = []
    
    @result.package = Result.new
    @result.package.types = []
    
    # Return the input as 'raw'
    @result.raw = @args['path']
    
    @path = Pathname.new @result.raw
    
    add_path_facts
    add_git_facts
    add_gem_facts
    add_npm_facts
    
    # If we only have one type of package present, we set it's type and 
    # version as `package.type` and `package.version`, which makes it easy for
    # code to 'auto-detect' the info.
    # 
    # If there's more than one then we obviously can't guess what to do; the
    # user needs to specify.
    # 
    if @result.package.types.length == 1
      @result.package.type = @result.package.types[0]
      @result.package.name = @result[@result.package.type].name
      @result.package.version = @result[@result.package.type].version
    end
    
    nil
  end
  
  def done
    exit_json @result.to_h
  end
end

PathFacts.new.run