require 'faraday/error'
require 'travis/services/base'

# TODO extract github specific stuff to a separate service

module Travis
  module Services
    class FindAdmin < Base
      extend Travis::Instrumentation
      include Travis::Logging

      register :find_admin

      def run
        if repository
          admin = candidates.first
          admin || raise_admin_missing
        else
          error "[github-admin] repository is nil: #{params.inspect}"
          raise Travis::RepositoryMissing, "no repository given"
        end
      end
      instrument :run

      def repository
        params[:repository]
      end

      private

        def candidates
          User.with_github_token.with_permissions(:repository_id => repository.id, :admin => true)
        end

        def validate(user)
          Timeout.timeout(2) do
            data = Github.authenticated(user) { repository_data }
            if data['permissions'] && data['permissions']['admin']
              user
            else
              info "[github-admin] #{user.login} no longer has admin access to #{repository.slug}"
              update(user, data['permissions'])
              false
            end
          end
        rescue Timeout::Error => error
          handle_error(user, error)
          false
        rescue GH::Error => error
          handle_error(user, error)
          false
        end

        def handle_error(user, error)
          status = error.info[:response_status]
          case status
          when 401
            error "[github-admin] token for #{user.login} no longer valid"
            user.update_attributes!(:github_oauth_token => "")
          when 404
            info "[github-admin] #{user.login} no longer has any access to #{repository.slug}"
            update(user, {})
          else
            error "[github-admin] error retrieving repository info for #{repository.slug} for #{user.login}: #{error.message}"
          end
        end

        # TODO should this not be memoized?
        def repository_data
          data = GH["repos/#{repository.slug}"]
          info "[github-admin] could not retrieve data for #{repository.slug}" unless data
          data || { 'permissions' => {} }
        end

        def update(user, permissions)
          user.update_attributes!(:permissions => permissions)
        end

        def raise_admin_missing
          raise Travis::AdminMissing.new("no admin available for #{repository.slug}")
        end

        class Instrument < Notification::Instrument
          def run_completed
            publish(
              msg: "for #{target.repository.slug}: #{result.login}",
              result: result
            )
          end
        end
        Instrument.attach_to(self)
    end
  end
end
