#!/usr/bin/env ruby

require 'json'
require 'faraday'
require 'faraday_middleware'
require 'fileutils'

module Guru
  class Deploy
    def initialize(project, branch, artifact, build_version, author, release_stage='staging', twilight_build_id, guru_addr, guru_access_key, commit_hash)
      @project = project
      @branch = branch
      @artifact = artifact
      @build_version = build_version
      @author = author.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
      @release_stage = release_stage
      @twilight_build_id = twilight_build_id
      @guru_addr = guru_addr
      @guru_access_key = guru_access_key
      @commit_hash = commit_hash

      begin
        @conn = Faraday.new(url: guru_addr) do |conn|
          conn.request :multipart
          conn.request :url_encoded
          conn.response :json, content_type: 'application/json'
          conn.adapter :net_http
          conn.headers = { 'UploadKey' => @guru_access_key }
        end
      rescue
        teamcity_status 1, "Initial: Could not connect to guru on #{guru_addr}"
      end
    end

    def submit
      rename_artifact
      generate_build_file
      payload = {
          :files => [
              Faraday::UploadIO.new('build.json', 'application/json'),
              Faraday::UploadIO.new('artifact.tar.gz', 'application/x-gzip')
          ]
      }

      begin
        resp = @conn.post do |req|
          req.url '/deployment/upload-build'
          req.body = payload
        end
      rescue
        teamcity_status 1, 'submit: Could not connect to guru'
      end

      if resp.status >= 300
        teamcity_status 1, "Got #{resp.status} from /deployment/upload-build: #{resp.body}"
      else
        teamcity_parameter('env.GURU_RELEASE_ID', resp.body['id']);
        teamcity_message "Guru deploy submitted by #{@author}"
        teamcity_status 0, "Artifact for build #{@build_version} on branch #{@branch} submitted to guru"
      end
    end

    def rename_artifact
      FileUtils.mv(@artifact, 'artifact.tar.gz')
    end

    def generate_build_file
      teamcity_message build_info
      File.open('build.json', 'w') do |f|
        f.write build_info
      end
    end

    def url_friendly_branch
      @branch.tr('_/', '-')
    end

    def build_info
      {
          Author: @author,
          BuildVersion: @build_version,
          ReleaseChannel: url_friendly_branch,
          ReleaseStage: @release_stage,
          BuildId: @twilight_build_id,
          CommitHash: @commit_hash
      }.to_json
    end

    # teamcity utility methods
    def teamcity_status(exit_code, message)
      puts "##teamcity[buildStatus status='#{exit_code.zero? ? 'Success' : 'Failed'}' text='#{message}']"
      exit exit_code
    end

    def teamcity_message(message, name = nil)
      puts "##teamcity[#{name || 'message'} text='#{message}']"
    end

    def teamcity_parameter(key, value)
      puts "##teamcity[setParameter name='#{key}' value='#{value}']"
    end
  end
end

# Need to set the content disposition name, and UploadIO/Multipart doesn't seem to support the correct format, hardcode it
module Parts
  module Part #:nodoc:
    def self.new(boundary, name, value, headers = {})
      headers ||= {} # avoid nil values
      if file?(value)
        FilePart.new(boundary, 'files', value, headers)
      else
        ParamPart.new(boundary, 'files', value, headers)
      end
    end
  end
end


guru_runner = Guru::Deploy.new(ENV['TEAMCITY_PROJECT_NAME'], ENV['BRANCH'], ENV['ARTIFACT_PATH'], ENV['IMAGE_VERSION'], ENV['AUTHOR'], ENV['RELEASE_STAGE'], ENV['TWILIGHT_BUILD_ID'], ENV['GURU_ADDR'], ENV['GURU_ACCESS_KEY'], ENV['BUILD_VCS_NUMBER'])
guru_runner.submit
