require 'restclient'
require 'nokogiri'
require 'yaml'
require 'json'

module NexusCli
  class OSSRemote

    def initialize(overrides)
      @configuration = Configuration::parse(overrides)
    end

    def configuration
      return @configuration if @configuration
    end

    def nexus
      @nexus ||= RestClient::Resource.new configuration["url"], :user => configuration["username"], :password => configuration["password"], :timeout => 1000000, :open_timeout => 1000000
    end

    def status
      doc = Nokogiri::XML(nexus['service/local/status'].get).xpath("/status/data")
      data = Hash.new
      data['app_name'] = doc.xpath("appName")[0].text
      data['version'] = doc.xpath("version")[0].text
      data['edition_long'] = doc.xpath("editionLong")[0].text
      data['state'] = doc.xpath("state")[0].text
      data['started_at'] = doc.xpath("startedAt")[0].text
      data['base_url'] = doc.xpath("baseUrl")[0].text
      return data
    end

    def pull_artifact(artifact, destination)
      # Using net/http because restclient dies on large files.
      group_id, artifact_id, version, extension = parse_artifact_string(artifact)
      uri = URI(File.join(configuration["url"], "service/local/artifact/maven/redirect"))
      params = {:g => group_id, :a => artifact_id, :v => version, :e => extension, :r => configuration["repository"]}.collect {|k,v| "#{k}=#{URI::escape(v.to_s)}"}.join("&")
      version = Nokogiri::XML(get_artifact_info(artifact)).xpath("//version").first.content() if version.casecmp("latest")
      destination = File.join(File.expand_path(destination || "."), "#{artifact_id}-#{version}.#{extension}")
      Net::HTTP.start(uri.host, uri.port) do |http|
        request = Net::HTTP::Get.new(URI(http.request_get(uri.request_uri + "?" + params)["location"]).request_uri)
        request.basic_auth(configuration["username"], configuration["password"])
        http.request(request) do |response|
          case response.code.to_i
          when 404
            raise ArtifactNotFoundException
          end
          artifact_file = File.open(destination, "wb") do |io|
            response.read_body do |chunk|
              io.write(chunk)
            end
          end
        end
      end
      File.expand_path(destination)
    end

    def push_artifact(artifact, file)
      group_id, artifact_id, version, extension = parse_artifact_string(artifact)
      nexus['service/local/artifact/maven/content'].post(:hasPom => false, :g => group_id, :a => artifact_id, :v => version, :e => extension, :p => extension, :r => configuration['repository'],
      :file => File.new(file)) do |response|
        case response.code
        when 400
          raise BadUploadRequestException
        when 401
          raise PermissionsException
        when 403
          raise PermissionsException
        when 404
          raise CouldNotConnectToNexusException
        end
      end
    end

    def delete_artifact(artifact)
      group_id, artifact_id, version = parse_artifact_string(artifact)
      delete_string = "content/repositories/releases/#{group_id.gsub(".", "/")}/#{artifact_id.gsub(".", "/")}/#{version}"
      Kernel.quietly {`curl --request DELETE #{File.join(configuration['url'], delete_string)} -u #{configuration['username']}:#{configuration['password']}`}
    end

    def get_artifact_info(artifact)
      group_id, artifact_id, version, extension = parse_artifact_string(artifact)
      begin
        nexus['service/local/artifact/maven/resolve'].get(:params => {:r => configuration['repository'], :g => group_id, :a => artifact_id, :v => version, :e => extension})
      rescue Errno::ECONNREFUSED => e
        raise CouldNotConnectToNexusException
      rescue RestClient::ResourceNotFound => e
        raise ArtifactNotFoundException
      end
    end

    def search_for_artifacts(artifact)
      group_id, artifact_id = artifact.split(":")
      nexus['service/local/data_index'].get(:params => {:g => group_id, :a => artifact_id}) do |response|
        doc = Nokogiri::XML(response.body)
        return format_search_results(doc, group_id, artifact_id)
      end
    end

    def get_global_settings
      json = get_global_settings_json
      pretty_json = JSON.pretty_generate(JSON.parse(json))
      Dir.mkdir(File.expand_path("~/.nexus")) unless Dir.exists?(File.expand_path("~/.nexus"))
      destination = File.join(File.expand_path("~/.nexus"), "global_settings.json")
      artifact_file = File.open(destination, 'wb') do |file|
        file.write(pretty_json)
      end
    end

    def upload_global_settings(json=nil)
      global_settings = nil
      if json == nil
        global_settings = File.read(File.join(File.expand_path("~/.nexus"), "global_settings.json"))
      else
        global_settings = json
      end
      nexus['service/local/global_settings/current'].put(global_settings, {:content_type => "application/json"}) do |response|
        case response.code
        when 400
          raise BadSettingsException.new(response.body)
        end
      end
    end

    def get_global_settings_json
      nexus['service/local/global_settings/current'].get(:accept => "application/json")
    end

    def reset_global_settings
      default_json = nexus['service/local/global_settings/default'].get(:accept => "application/json")
      nexus['service/local/global_settings/current'].put(default_json, :content_type => "application/json")
    end

    def create_repository(name)
      nexus['service/local/repositories'].post(create_repository_json(name), :content_type => "application/json") do |response|
        case response.code
        when 400
          raise CreateRepsitoryException.new(response.body)
        when 201
          return true
        else
          raise UnexpectedStatusCodeException.new(response.code)
        end
      end
    end

    def delete_repository(name)
      nexus["service/local/repositories/#{name.downcase}"].delete do |response|
        case response.code
        when 404
          raise RepositoryDoesNotExistException
        when 204
          return true
        else
          raise UnexpectedStatusCodeException.new(response.code)
        end
      end
    end

    def get_repository_info(name)
      begin
        nexus["service/local/repositories/#{name.downcase}"].get
      rescue Errno::ECONNREFUSED => e
        raise CouldNotConnectToNexusException
      rescue RestClient::ResourceNotFound => e
        raise RepositoryNotFoundException
      end
    end

    private
    def format_search_results(doc, group_id, artifact_id)
      versions = doc.xpath("//version").inject([]) {|array,node| array << "#{node.content()}"}
      indent_size = versions.max{|a,b| a.length <=> b.length}.size+4
      formated_results = ['Found Versions:']
      versions.inject(formated_results) do |array,version|
        temp_version = version + ":"
        array << "#{temp_version.ljust(indent_size)} `nexus-cli pull #{group_id}:#{artifact_id}:#{version}:tgz`"
      end
    end

    def parse_artifact_string(artifact)
      split_artifact = artifact.split(":")
      if(split_artifact.size < 4)
        raise ArtifactMalformedException
      end
      group_id, artifact_id, version, extension = split_artifact
      version.upcase! if version.casecmp("latest")
      return group_id, artifact_id, version, extension
    end

    def create_repository_json(name)
      %{
        {
          "data" : {
            "provider" : "maven2",
            "providerRole" : "org.sonatype.nexus.proxy.repository.Repository",
            "exposed" : true,
            "repoType" : "hosted",
            "repoPolicy" : "RELEASE",
            "name" : #{name},
            "id" : #{name.downcase},
            "format" : "maven2"
          }
        }
      }
    end
  end
end