require 'rake/tasklib'
require 'open3'

module XCJobs
  module Distribute
    def upload(url, form_data = {}, header = {})
      @before_action.call if @before_action

      curl_options = ['curl', '-sSf', "#{url}"]
      form_fields = form_data.flat_map { |k, v| ['-F', "#{k}=#{v}"] }
      header_fields = header.flat_map { |k, v| ['-H', "#{k}:#{v}"] }
      puts (curl_options + form_fields + header_fields).join(' ')
      Open3.popen2e({ "NSUnbufferedIO" => "YES" }, *(curl_options + form_fields + header_fields)) do |stdin, stdout_err, wait_thr|
        output = ''
        while line = stdout_err.gets
          puts line
          output << line
        end

        status = wait_thr.value
        if status.success?
          @after_action.call(output, status) if @after_action
        else
          fail "upload failed (exited with status: #{status.exitstatus})"
        end
      end
    end

    def before_action(&block)
      @before_action = block
    end

    def after_action(&block)
      @after_action = block
    end

    class TestFlight < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :file
      attr_accessor :api_token
      attr_accessor :team_token
      attr_accessor :notify
      attr_accessor :replace
      attr_accessor :distribution_lists
      attr_accessor :notes

      def initialize()
        yield self if block_given?
        define
      end

      private

      def define
        namespace :distribute do
          desc 'upload IPA to TestFlight'
          task :testflight do
            upload('http://testflightapp.com/api/builds.json', form_data)
          end
        end
      end

      def form_data
        {}.tap do |fields|
          fields[:file] = "@#{file}" if file
          fields[:api_token] = api_token if api_token
          fields[:team_token] = team_token if team_token
          fields[:notify] = notify if notify
          fields[:replace] = replace if replace
          fields[:distribution_lists] = distribution_lists if distribution_lists
          fields[:notes] = notes if notes
        end
      end
    end

    class DeployGate < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :owner_name

      attr_accessor :token
      attr_accessor :file
      attr_accessor :message
      attr_accessor :distribution_key
      attr_accessor :release_note
      attr_accessor :disable_notify
      attr_accessor :visibility

      def initialize()
        yield self if block_given?
        define
      end

      private

      def define
        namespace :distribute do
          desc 'upload IPA to DeployGate'
          task :deploygate do
            upload("https://deploygate.com/api/users/#{owner_name}/apps", form_data)
          end
        end
      end

      def form_data
        {}.tap do |fields|
          fields[:token] = token if token
          fields[:file] = "@#{file}" if file
          fields[:message] = message if message
          fields[:distribution_key] = distribution_key if distribution_key
          fields[:release_note] = release_note if release_note
          fields[:disable_notify] = 'yes' if disable_notify
          fields[:visibility] = visibility if visibility
        end
      end
    end

    class Crashlytics < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :framework_path
      attr_accessor :file
      attr_accessor :api_key
      attr_accessor :build_secret
      attr_accessor :notes
      attr_accessor :notifications

      def initialize()
        @notifications = true
        @emails = []
        @group_aliases = []
        yield self if block_given?
        define
      end

      def add_email(email)
        @emails << email
      end

      def add_group_alias(group_alias)
        @group_aliases << group_alias
      end

      private

      def define
        namespace :distribute do
          desc 'upload IPA to Beta by Crashlytics'
          task :crashlytics do
            @before_action.call if @before_action
            sh *(["#{File.join(framework_path, 'submit')}"] + options)
            @after_action.call('', SystemExit.new) if @after_action
          end
        end
      end

      def options
        [].tap do |opts|
          opts << api_key
          opts << build_secret
          opts.concat(['-ipaPath', file]) if file
          opts.concat(['-notifications', 'NO']) unless notifications
          opts.concat(['-emails', @emails.join(',')]) unless @emails.empty?
          opts.concat(['-groupAliases', @group_aliases.join(',')]) unless @group_aliases.empty?
          if notes
            temp = Tempfile.new('release_notes.txt')
            temp.puts(notes)
            temp.flush
            opts.concat(['-notesPath', temp.path])
          end
        end
      end
    end

    class HockeyApp < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :file
      attr_accessor :dsym
      attr_accessor :token
      attr_accessor :identifier
      attr_accessor :notes
      attr_accessor :notes_type
      attr_accessor :notify
      attr_accessor :status
      attr_accessor :tags
      attr_accessor :teams
      attr_accessor :users
      attr_accessor :mandatory
      attr_accessor :commit_sha
      attr_accessor :build_server_url
      attr_accessor :repository_url

      def initialize()
        yield self if block_given?
        define
      end

      private

      def define
        namespace :distribute do
          desc 'upload IPA & dSYMs to HockeyApp'
          task :hockeyapp do
            upload("https://rink.hockeyapp.net/api/2/apps/#{identifier}/app_versions/upload", form_data, header)
          end
        end
      end

      def form_data
        {}.tap do |fields|
          fields[:ipa] = "@#{file}" if file
          fields[:dsym] = "@#{dsym}" if dsym
          fields[:notes] = notes if notes
          fields[:notes_type] = notes_type if notes_type
          fields[:notify] = notify if notify
          fields[:status] = status if status
          fields[:tags] = tags.join(',') if tags
          fields[:teams] = teams.join(',') if teams
          fields[:users] = users.join(',') if users
          fields[:mandatory] = mandatory if mandatory
          fields[:commit_sha] = commit_sha if commit_sha
          fields[:build_server_url] = build_server_url if build_server_url
          fields[:repository_url] = repository_url if repository_url
        end
      end

      def header
        {}.tap do |fields|
          fields["X-HockeyAppToken"] = token if token
        end
      end
    end

    class Crittercism < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :app_id
      attr_accessor :dsym
      attr_accessor :key

      def initialize()
        yield self if block_given?
        define
      end

      private

      def define
        namespace :distribute do
          desc 'upload dSYMs to Crittercism'
          task :crittercism do
            upload("https://api.crittercism.com/api_beta/dsym/#{app_id}", form_data)
          end
        end
      end

      def form_data
        {}.tap do |fields|
          fields[:dsym] = "@#{dsym}" if dsym
          fields[:key] = key if key
        end
      end
    end

    class ITC < Rake::TaskLib
      include Rake::DSL if defined?(Rake::DSL)
      include Distribute

      attr_accessor :file
      attr_accessor :username
      attr_accessor :password
      attr_accessor :altool

      def initialize()
        yield self if block_given?
        define
      end

      def altool
        @altool || '/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool'
      end

      private

      def define
        namespace :distribute do
          desc 'upload ipa to iTunes Connect'
          task :itc do
            sh *["#{altool}", '--upload-app', '--file', "#{file}", '--username', "#{username}", '--password', "#{password}"]
          end
        end
      end
    end

  end
end
