"""
Connection Object that uses Python Requests

Requires: requests, requests_toolbelt,
Possible optional might be required: requests_ntlm, requests_kerberos, requests-oauthlib

"""
try:
    import arcpy
    HASARCPY = True
except ImportError:
    HASARCPY = False

import os
import sys
import copy
import json
import uuid
import datetime
import mimetypes
import tempfile
from urllib.request import urlparse, unquote, urljoin
import requests
from requests import Session
from requests_toolbelt.downloadutils import stream
from requests_toolbelt.multipart.encoder import MultipartEncoder
from ._helpers import _filename_from_headers, _filename_from_url
from ._authguess import GuessAuth
from arcgis._impl.common._mixins import PropertyMap
from arcgis._impl.common._isd import InsensitiveDict

__version__ = "1.8.0"

_DEFAULT_TOKEN = uuid.uuid4()

class Connection(object):
    """
    Universal Connection Object
    """
    _refresh_token = None
    _token = None
    _token_url = None
    _create_time = None
    _session = None
    _header = None
    _username = None
    _password = None
    _proxy_url = None
    _proxy_port = None
    _proxy_password = None
    _proxy_username = None
    _verify_cert = None
    _baseurl = None
    baseurl = None
    _referer = None
    _expiration = None
    _auth = None
    _product = None
    _custom_auth = None
    #----------------------------------------------------------------------
    def __init__(self,
                 baseurl=None,
                 username=None,
                 password=None,
                 **kwargs):
        """initializer

        Optional Kwargs

        client_id=None
        client_secret=None
        expiration=60
        cert_file
        key_file
        proxy_url
        proxy_port
        verify_cert
        referer
        all_ssl = True
        portal_connection = None
        product = "UNKNOWN", "PORTAL", "SERVER", "AGOL", "GEOEVENT"  # SHOULD BE SET BY GIS object, or Server Object, etc...
        token= None If token, _AUTH = BUILTIN
        token_url
        AUTH keys = HOME, BUILTIN, PRO, ANON, PKI, HANDLER, UNKNOWN (Internal)
        custom_auth = Requests authencation handler
        """
        from arcgis.gis import GIS
        self._all_ssl = kwargs.pop("all_ssl", True)
        if baseurl:
            while baseurl.endswith("/"):
                baseurl = baseurl[:-1]
        self._custom_auth = kwargs.pop('custom_auth', None)
        self._product = kwargs.pop('product', "UNKNOWN")
        if baseurl is None:
            self._product = "AGOL"
            baseurl = "https://www.arcgis.com"


        self._baseurl = baseurl
        self._username = username
        self._password = password

        self._expiration = kwargs.pop('expiration', 60) or 60
        self._portal_connection = kwargs.pop('portal_connection', None) # For Federated Objects (Portal Connection)
        if isinstance(self._portal_connection, GIS):
            self._portal_connection = self._portal_connection._con

        if (self._referer or self._referer is None) and \
           self._portal_connection and \
           str(self._portal_connection._auth).lower() == "home":
            self._referer = None
        elif baseurl.lower() == 'pro':
            try:
                self._referer = arcpy.GetSigninToken().pop('referer', 'http')
            except:
                self._referer = kwargs.pop('referer', 'http')
        else:
            self._referer = kwargs.pop('referer', 'http')

        self._verify_cert = kwargs.pop("verify_cert", False)#True)
        if self._verify_cert == False:
            import warnings
            from requests.packages.urllib3 import exceptions
            warnings.simplefilter("ignore", exceptions.InsecureRequestWarning)

        self._cert_file = kwargs.pop('cert_file', None)
        self._key_file = kwargs.pop('key_file', None)
        if 'proxy_host' in kwargs:
            self._proxy_url = kwargs.pop('proxy_host', None)
        else:
            self._proxy_url = kwargs.pop('proxy_url', None)
        self._proxy_port = kwargs.pop('proxy_port', None)
        self._proxy_username = kwargs.pop('proxy_username', None)
        self._proxy_password = kwargs.pop('proxy_password', None)
        self._header = {'User-Agent' : 'Geosaurus/%s' % __version__}
        self._token = kwargs.pop('token', None)
        self._client_id = kwargs.pop('client_id', None)
        self._client_secret = kwargs.pop('client_secret', None)
        self._token_url = kwargs.pop('token_url', None)

        if username is None and password is None and self._portal_connection is None:
            self._auth = "ANON"
        elif ((not username is None and not password is None) or self._portal_connection):
            self._auth = "BUILTIN"
        if (username and password) and \
           self._client_id is None and \
           str(baseurl).lower() != "pro":
            self._auth = "BUILTIN"
        elif self._portal_connection:
            self._auth = "BUILTIN"
        elif baseurl.lower() == 'pro':
            self._auth = "PRO"
            self._baseurl = arcpy.GetActivePortalURL()
        elif self._cert_file or\
             (self._cert_file and self._key_file):
            self._auth = "PKI"

        self._create_session()

        #  Product Info
        if self._token:
            if self._expiration is None:
                self._expiration = 60
            self._create_time = datetime.datetime.now() + datetime.timedelta(minutes=self._expiration)
            self._auth = "BUILTIN"
        elif self._client_id:
            self._product = self._check_product()
            if self._product in ['PORTAL', "AGOL"]:
                resp = self.post("/portals/self", {'f' : 'json'}, add_token=False)
                issaml = resp.get("samlEnabled", False)
                isoauth = resp.get("supportsOAuth", False)
            else:
                resp = None
                issaml = False
                isoauth = False
            self._auth = 'OAUTH'
            parsed = urlparse(self._baseurl)
            wa = parsed.path
            if wa.startswith('/'):
                wa = wa[1:].split('/')[0]
            else:
                wa = wa.split('/')[0]
            if len(wa) > 0:
                self._token_url = "https://%s/%s/sharing/rest/oauth2/token" % (parsed.netloc, wa)
            else:
                self._token_url = "https://%s/sharing/rest/oauth2/token" % (parsed.netloc)

        else:
            self._product = self._check_product()
        self._baseurl = self._validate_url(self._baseurl)
        self.baseurl = self._baseurl
    #----------------------------------------------------------------------
    def _validate_url(self, url):
        """ensures the base url has the /sharing/rest"""
        if url.lower().find('arcgis.com') > -1:
            self._product = "AGOL"
        if self._product in ['AGO', 'AGOL', 'PORTAL']:
            if not url[-1] == '/':
                url += '/'
            if url.lower().find("www.arcgis.com") > -1:
                urlscheme = urlparse(url).scheme
                return "{scheme}://www.arcgis.com/sharing/rest/".format(scheme=urlscheme)
            elif url.lower().endswith("sharing/"):
                return url + 'rest/'
            elif url.lower().endswith("sharing/rest/"):
                return url
            else:
                return url + 'sharing/rest/'
        elif self._product in ['SERVER', 'FEDERATED_SERVER', "FEDERATED"]:
            parsed = urlparse(url)
            path = parsed.path
            if str(path).startswith("/"):
                path = path[1:]
            url = "%s://%s/%s/rest/services/" % (parsed.scheme,
                                                 parsed.netloc,
                                                 path.split('/')[0])
        return url
    #----------------------------------------------------------------------
    def _create_session(self):
        if self._proxy_port and self._proxy_url:
            url = "%s:%s" % (self._proxy_url, self._proxy_port)
            if self._proxy_password and self._proxy_username:
                proxies = {'http': 'http://%s:%s@%s' % (self._proxy_username,
                                                        self._proxy_password,
                                                        url),
                           'https': 'https://%s:%s@%s' % (self._proxy_username,
                                                          self._proxy_password,
                                                          url)}
            else:
                proxies = {'http': 'http://%s' % url,
                           'https': 'https://%s' % url}
        else:
            proxies = None

        self._session = Session()
        self._session.verify = self._verify_cert
        self._session.stream = True
        self._session.headers.update(self._header)
        self._session.proxies = proxies

        if self._referer is None and\
           (self._portal_connection and \
           str(self._portal_connection._auth).lower() == "home"):
            self._referer = "http"
            self._session.headers.pop("Referer", None)
            self._session.headers['Referer'] = json.dumps("")
        elif (self._portal_connection and str(self._portal_connection._auth).lower() == "home"):
            self._referer = "http"
            self._session.headers.pop("Referer", None)
            self._session.headers['Referer'] = json.dumps("")
        elif self._referer is None:
            self._referer = 'http'
            self._session.headers.update({'Referer': self._referer})
        else:
            self._session.headers.update({'Referer': self._referer})
        if self._custom_auth:
            self._session.auth = self._custom_auth
            self._auth = "CUSTOM"
        elif self._username and self._password:
            self._session.auth = GuessAuth(username=self._username,
                                           password=self._password)
        elif self._auth.lower() == "pro":
            self._session.auth = GuessAuth(None, None)
        else:
            try:
                from .include.requests_negotiate_sspi import HttpNegotiateAuth
                HAS_KERBEROS = True
            except:
                HAS_KERBEROS = False
            if HAS_KERBEROS:
                self._session.auth = HttpNegotiateAuth()
            else:
                try:
                    from requests_kerberos import HTTPKerberosAuth, DISABLED
                    self._session.auth = HTTPKerberosAuth(DISABLED)
                except ImportError:
                    pass
                except Exception as e:
                    raise e
        if self._cert_file and self._key_file:
            self._session.cert = (self._cert_file,
                                  self._key_file)
        elif self._cert_file and self._password:
            from arcgis.gis._impl._con._cert import pfx_to_pem
            self._key_file, self._cert_file = pfx_to_pem(pfx_path=self._cert_file, pfx_password=self._password)
            self._session.cert = (self._cert_file, self._key_file)
        elif self._cert_file:
            self._session.cert = (self._cert_file)
        else:
            self._session.cert = None
    #----------------------------------------------------------------------
    def get(self,
            path,
            params=None,
            **kwargs):
        """

        sends a GET request.

        ===========================   =====================================================
        **optional keywords**         **description**
        ---------------------------   -----------------------------------------------------
        path                          required string. URI resource to access.
        ===========================   =====================================================

        ===========================   =====================================================
        **optional keywords**         **description**
        ---------------------------   -----------------------------------------------------
        params                        optional dictionary.  This is the data payload to the
                                      web resource
        ---------------------------   -----------------------------------------------------
        out_folder                    optional string. Save path on disk.  The default is
                                      the user's temp directory.
        ---------------------------   -----------------------------------------------------
        file_name                     optional string. Name of the file to save. If not
                                      provided, name will try to be parsed from the response.
        ---------------------------   -----------------------------------------------------
        try_json                      optional boolean.  If True, the marker f='json' will
                                      be appended to the parameters.
        ---------------------------   -----------------------------------------------------
        add_token                     optional boolean. Default True, if true, the call will
                                      add a token to any token based security.
        ---------------------------   -----------------------------------------------------
        json_encode                   optional Boolean.  When False, the JSON values will not be encoded.
        ===========================   =====================================================
        """
        json_encode = kwargs.pop('json_encode', True)
        if self._baseurl.endswith('/') == False:
            self._baseurl += "/"
        url = path
        token = kwargs.pop('token', _DEFAULT_TOKEN)
        token_as_header = kwargs.pop('token_as_header', False)
        token_header = kwargs.pop('token_header', "X-Esri-Authorization")
        if url.find('://') == -1:
            if url.startswith('/') == False and \
               self._baseurl.endswith('/') == False:
                url = "/" + url
            url = self._baseurl + url
        if kwargs.pop("ssl", False) or self._all_ssl:
            url = url.replace("http://", "https://")
        if params is None:
            params = {}
        if self._auth == "IWA":
            self._session = None
        if self._session is None:
            self._create_session()

        try_json = kwargs.pop("try_json", True)
        add_token = kwargs.pop('add_token', True)

        if add_token:
            if token != _DEFAULT_TOKEN:
                if token is not None:
                    params['token'] = token
                else:
                    params.pop('token', None)
                    #pass
            elif token_as_header == False and self.token is not None: #as ?token=
                params['token'] = self.token
            elif token_as_header and self.token is not None:#(token and token != _DEFAULT_TOKEN): # as X-Esri-Auth header with given token
                self._session.headers.update({token_header: "Bearer %s" % token})
            elif token_as_header and token_header and self.token: # as X-Esri-Auth header with generated token
                self._session.headers.update({token_header: "Bearer %s" % self.token})
        if try_json:
            params['f'] = 'json'
        if params == {}:
            params = None

        if 'out_folder' in kwargs:
            out_path = kwargs.pop('out_folder',
                                  tempfile.gettempdir())
        else:
            out_path = kwargs.pop('out_path',
                                  tempfile.gettempdir())
        file_name = kwargs.pop('file_name', None)
        if params and json_encode:
            for k, v in copy.copy(params).items():
                if isinstance(v, (tuple, dict, list, bool)):
                    params[k] = json.dumps(v)
                elif isinstance(v, PropertyMap):
                    params[k] = json.dumps(dict(v))
                elif isinstance(v, InsensitiveDict):
                    params[k] = v.json
        try:
            if self._cert_file:
                cert = (self._cert_file, self._key_file)
            else:
                cert = None
            if self._auth.lower() == 'pki':
                self._session.cookies.clear()
                params.pop('token', None)
            resp = self._session.get(url=url,
                                     params=params,
                                     cert=cert,
                                     verify=self._verify_cert)

        except requests.exceptions.SSLError as err:
            raise requests.exceptions.SSLError(
                "Please set verify_cert=False due to encountered SSL error: %s" % err)
        except requests.exceptions.InvalidURL as errIU:
            raise requests.exceptions.SSLError(
                "Invalid URL provided: %s" % errIU)
        except requests.exceptions.ConnectionError as errCE:
            raise requests.exceptions.ConnectionError(
                "A connection error has occurred: %s" % errCE)
        except requests.exceptions.InvalidHeader as errIH:
            raise requests.exceptions.InvalidHeader(
                "A invalid header was provided: %s" % errIH)
        except requests.exceptions.HTTPError as errh:
            raise requests.exceptions.HTTPError(
                "Http Error: %s" % errh)
        except requests.exceptions.MissingSchema as errMS:
            raise requests.exceptions.MissingSchema(
                "URL scheme must be provided: %s" % errMS)
        except requests.exceptions.RequestException as errRE:
            raise requests.exceptions.RequestException(
                "A general expection was raised: %s"  % errRE)
        except Exception as e:
            raise Exception('A general error occurred: %s' % e)
        except:
            import traceback
            raise Exception(
                'An unknown error occurred: %s' % traceback.format_exc())

        return self._handle_response(resp, file_name, out_path, try_json, force_bytes=kwargs.pop('force_bytes', False))
    #----------------------------------------------------------------------
    def _handle_response(self, resp, file_name, out_path, try_json, force_bytes=False):
        """
        handles the request responses

        ===========================   =====================================================
        **optional keywords**         **description**
        ---------------------------   -----------------------------------------------------
        resp                          required resquests.Rsponse object.
        ---------------------------   -----------------------------------------------------
        file_name                     required string.  Name of the output file if needed.
        ---------------------------   -----------------------------------------------------
        out_path                      required string. Name of the save folder.
        ---------------------------   -----------------------------------------------------
        try_json                      required boolean. Determines if the response should
                                      be returned as a dictionary or the native format
                                      send back by a server
        ---------------------------   -----------------------------------------------------
        force_bytes                   Optional Boolean. If True, the results returns as bytes
                                      instead of a file path.
        ===========================   =====================================================

        returns: string, dictionary, or bytes depending on the response.

        """
        if 'Set-Cookie' in resp.headers and \
           (str(self._auth).lower() in ['anon'] or \
            (str(self._username).find("\\") > -1 and str(self._auth).lower() in ['builtin'])):
            self._auth = 'IWA'

        data = None
        url = resp.url
        if out_path and \
           os.path.isdir(out_path) == False:
            os.makedirs(out_path)
        if out_path is None:
            out_path = tempfile.gettempdir()
        if file_name is None and \
           (resp.headers['Content-Type'].lower().find('json') == -1 and \
           resp.headers['Content-Type'].lower().find('text') == -1):
            file_name = _filename_from_url(url) or _filename_from_headers(
                resp.headers) or None
        if force_bytes:
            try:
                return bytes(resp.content)
            except:
                return resp.content
        if file_name is not None:
            file_name = os.path.join(out_path, file_name)
            if os.path.isfile(file_name):
                os.remove(file_name)
            fp = stream.stream_response_to_file(response=resp,
                                                path=file_name,
                                                chunksize=512*2)
            return fp

        if try_json:
            if 'Content-Length' in resp.headers and int(resp.headers.get('Content-Length')) == 0:
                data = {}
            elif 'Transfer-Encoding' in resp.headers and \
               resp.headers['Transfer-Encoding'].lower() == 'chunked':
                data = None
                for it in resp.iter_lines(chunk_size=None, decode_unicode=True, delimiter=None):
                    if data is None:
                        data = it
                    else:
                        data += it
                data = json.loads(data)
                if 'error' in data:
                    raise Exception(resp['error'])
            else:
                data = resp.json()
            #if 'error' in data:
                #raise Exception(data['error'])
            #return data
        #else:
            #return resp.text
            if 'error' in data:
                errorcode = data['error']['code'] if 'code' in data['error'] else 0
                self._handle_json_error(data['error'], errorcode)
            return data
        else:
            return resp.text
    #----------------------------------------------------------------------
    def _handle_json_error(self, error, errorcode):
        errormessage = error.get('message', 'Unknown Error')
        #_log.error(errormessage)
        if 'details' in error and error['details'] is not None:
            if isinstance(error['details'], str):
                errormessage = f"{errormessage} \n {error['details']}"
                #_log.error(error['details'])
            else:
                for errordetail in error['details']:
                    errormessage = errormessage + "\n" + errordetail
                    #_log.error(errordetail)

        errormessage = errormessage + "\n(Error Code: " + str(errorcode) +")"
        raise Exception(errormessage)
    #----------------------------------------------------------------------
    def post(self,
             path,
             params=None,
             files=None,
             **kwargs):
        """
        sends a POST request.

        ===========================   =====================================================
        **Parameters**                **Description**
        ---------------------------   -----------------------------------------------------
        path                          optional string.  URL or part of the url resource
                                      to call.
        ---------------------------   -----------------------------------------------------
        params                        optional dict.  Contains data to pass to the web
                                      resource.
        ---------------------------   -----------------------------------------------------
        files                         optional list of files

                                      Files can be provided two ways:

                                      The most basic way is:
                                      Way1: {key : r"c:\temp\myfile.foo}
                                      This is just the file path and the key.


                                      The preferred way:

                                      Way 2: {key : (file_name, open(c:\temp\myfile.foo, 'rb'), image\jpeg)}

                                      Way 2 requires providing the filename, IO object as 'rb', and the mimetype.
        ===========================   =====================================================


        ===========================   =====================================================
        **Optional Parameters**       **Description**
        ---------------------------   -----------------------------------------------------
        add_token                     optional boolean.  True means try to add the boolean,
                                      else do not add a ?token=<foo> to the call. If
                                      is_geoevent is True, then the token will be appended
                                      to the header.
        ---------------------------   -----------------------------------------------------
        is_geoevent                   optional boolean. True means the auth token will be placed in the header
        ---------------------------   -----------------------------------------------------
        try_json                      optional boolean.  If true, the call adds the ?f=json.
        ---------------------------   -----------------------------------------------------
        ssl                           optional boolean. If true all calls are forced to be
                                      https.
        ---------------------------   -----------------------------------------------------
        out_folder                    optional string.  This is the save folder for the data.
        ---------------------------   -----------------------------------------------------
        file_name                     optional string. The save name of the file. This will override the file name if provided in the response.
        ---------------------------   -----------------------------------------------------
        force_bytes                   optional boolean.  Deprecated.
        ---------------------------   -----------------------------------------------------
        add_headers                   optional dict.  If provided, additional headers will be given for a single call.
        ---------------------------   -----------------------------------------------------
        post_json                     optional bool. If True, the data is pushed in the request's json parameter.  This is an edge case for Workflow Manager. The default is `False`.
        ---------------------------   -----------------------------------------------------
        json_encode                   optional Bool. If False, the key/value parameters will not be JSON encoded.
        ===========================   =====================================================

        :returns: data returned from the URL call.

        """
        json_encode = kwargs.pop("json_encode", True)
        if self._baseurl.endswith("/") == False:
            self._baseurl += "/"
        url = path
        token = kwargs.pop('token', _DEFAULT_TOKEN)
        post_json = kwargs.pop("post_json", False)
        token_as_header = kwargs.pop('token_as_header', False)
        token_header = kwargs.pop('token_header', "X-Esri-Authorization")
        if self._auth == "IWA":
            self._session = None
        if 'postdata' in kwargs: # handles legacy issues
            params = kwargs.pop('postdata')
        if params is None:
            params = {}
        if self._auth.lower() == "PKI":
            self._session = None
        if self._session is None:
            self._create_session()
        try_json = kwargs.pop("try_json", True)
        add_token = kwargs.pop('add_token', True)
        if url.find('://') == -1:
            url = self._baseurl + url
        if kwargs.pop("ssl", False) or self._all_ssl:
            url = url.replace("http://", "https://")
        if add_token:
            if token != _DEFAULT_TOKEN:
                if token is not None:
                    params['token'] = token
                else:
                    params.pop('token', None)
                    #pass
            elif token_as_header == False and self.token is not None: #as ?token=
                params['token'] = self.token
            elif token_as_header and self.token is not None:#(token and token != _DEFAULT_TOKEN): # as X-Esri-Auth header with given token
                self._session.headers.update({token_header: "Bearer %s" % token})
            elif token_as_header and token_header and self.token: # as X-Esri-Auth header with generated token
                self._session.headers.update({token_header: "Bearer %s" % self.token})
        if try_json:
            params['f'] = 'json'
        if files:
            fields = { }
            if isinstance(files, dict):
                for k, v in files.items():
                    if isinstance(v, (list, tuple)):
                        fields[k] = v
                    else:
                        fields[k] = (os.path.basename(v),
                                     open(v, 'rb'),
                                     mimetypes.guess_type(v)[0])
            elif isinstance(files, (list, tuple)):
                for key, filePath, fileName in files:
                    if isinstance(fileName, str):
                        fields[key] = (fileName,
                                       open(filePath, 'rb'),
                                       mimetypes.guess_type(filePath)[0])
                    else:
                        fields[key] = v
            files = fields

        out_path = kwargs.pop('out_path', None) or \
            kwargs.pop('out_folder', None) or \
            tempfile.gettempdir()
        file_name = kwargs.pop('file_name', None)
        try:
            if self._cert_file:
                cert = (self._cert_file, self._key_file)
            else:
                cert = None
            if self._auth.lower() == 'pki':
                self._session.cookies.clear()
            if json_encode:
                for k,v in params.items():
                    if isinstance(v, (dict, list, tuple, bool)):
                        params[k] = json.dumps(v)
                    elif isinstance(v, PropertyMap):
                        params[k] = json.dumps(dict(v))
                    elif isinstance(v, InsensitiveDict):
                        params[k] = v.json
            if post_json:  # edge case workflow
                resp = self._session.post(url=url,
                                          json=params,
                                          cert=cert,
                                          files=files)
            else:
                resp = self._session.post(url=url,
                                          data=params,
                                          cert=cert,
                                          files=files)
        except requests.exceptions.SSLError as err:
            raise requests.exceptions.SSLError(
                "Please set verify_cert=False due to encountered SSL error: %s" % err)
        except requests.exceptions.InvalidURL as errIU:
            raise requests.exceptions.SSLError(
                "Invalid URL provided: %s" % errIU)
        except requests.exceptions.ConnectionError as errCE:
            raise requests.exceptions.ConnectionError(
                "A connection error has occurred: %s" % errCE)
        except requests.exceptions.InvalidHeader as errIH:
            raise requests.exceptions.InvalidHeader(
                "A invalid header was provided: %s" % errIH)
        except requests.exceptions.HTTPError as errh:
            raise requests.exceptions.HTTPError(
                "Http Error: %s" % errh)
        except requests.exceptions.RequestException as errRE:
            raise requests.exceptions.RequestException(
                "A general expection was raised: %s"  % errRE)
        except requests.exceptions.MissingSchema as errMS:
            raise requests.exceptions.MissingSchema(
                "URL scheme must be provided: %s" % errMS)
        except Exception as e:
            raise Exception('A general error occurred: %s' % e)
        except:
            import traceback
            raise Exception(
                'An unknown error occurred: %s' % traceback.format_exc())

        return self._handle_response(resp=resp,
                                     out_path=out_path,
                                     file_name=file_name,
                                     try_json=try_json,
                                     force_bytes=kwargs.pop('force_bytes', False))
    #----------------------------------------------------------------------
    def put(self, url, params=None, files=None, **kwargs):
        """
        sends a PUT request

        ===========================   =====================================================
        **Optional Parameters**       **Description**
        ---------------------------   -----------------------------------------------------
        url                           Optional String. The web endpoint.
        ---------------------------   -----------------------------------------------------
        params                        Optional dict. The Key/value pairs to send along with the request.
        ---------------------------   -----------------------------------------------------
        files                         Optional list. Allows users to provide a file or files
                                      to the operation.  The format should be:
                                      files = [[key, filePath, fileName], ...,[keyN, filePathN, fileNameN]]
        ===========================   =====================================================

        ===========================   =====================================================
        **Optional Parameters**       **Description**
        ---------------------------   -----------------------------------------------------
        add_token                     optional boolean.  True means try to add the boolean,
                                      else do not add a ?token=<foo> to the call.
        ---------------------------   -----------------------------------------------------
        token_as_header               Optional boolean.  If True, the token will go into
                                      the header as `Authorization` header.  This can be
                                      overwritten using the `token_header` parameter
        ---------------------------   -----------------------------------------------------
        token_header                  Optional String. If provided and token_as_header is
                                      True, authentication token will be placed in this
                                      instead on URL string.
        ---------------------------   -----------------------------------------------------
        try_json                      optional boolean.  If true, the call adds the ?f=json.
        ---------------------------   -----------------------------------------------------
        ssl                           optional boolean. If true all calls are forced to be
                                      https.
        ---------------------------   -----------------------------------------------------
        post_json                     optional bool. If True, the data is pushed in the request's json parameter.  This is an edge case for Workflow Manager. The default is `False`.
        ---------------------------   -----------------------------------------------------
        json_encode                   optional Bool. If False, the key/value parameters will not be JSON encoded.
        ===========================   =====================================================

        :returns: dict or string depending on the response

        """
        token = kwargs.pop('token', _DEFAULT_TOKEN)
        post_json = kwargs.pop("post_json", False)
        json_encode = kwargs.pop("json_encode", True)
        out_path = kwargs.pop('out_path', None)
        file_name = kwargs.pop('file_name', None)
        token_as_header = kwargs.pop('token_as_header', True)
        token_header = kwargs.pop('token_header', "X-Esri-Authorization")
        if params is None:
            params = {}
        if self._session is None:
            self._create_session()
        try_json = kwargs.pop("try_json", True)
        add_token = kwargs.pop('add_token', True)
        if url.find('://') == -1:
            url = self._baseurl + url
        if kwargs.pop("ssl", False):
            url = url.replace("http://", "https://")
        if add_token:
            if token != _DEFAULT_TOKEN:
                if token is not None:
                    params['token'] = token
                else:
                    params.pop('token', None)
                    #pass
            elif token_as_header == False and self.token is not None: #as ?token=
                params['token'] = self.token
            elif token_as_header and self.token is not None:#(token and token != _DEFAULT_TOKEN): # as X-Esri-Auth header with given token
                self._session.headers.update({token_header: "Bearer %s" % token})
            elif token_as_header and token_header and self.token: # as X-Esri-Auth header with generated token
                self._session.headers.update({token_header: "Bearer %s" % self.token})

        if try_json:
            params['f'] = 'json'

        if files:
            fields = { }
            if isinstance(files, dict):
                for k, v in files.items():
                    if isinstance(v, (list, tuple)):
                        fields[k] = v
                    else:
                        fields[k] = (os.path.basename(v),
                                     open(v, 'rb'),
                                     mimetypes.guess_type(v)[0])
            elif isinstance(files, (list, tuple)):
                for key, filePath, fileName in files:
                    if isinstance(fileName, str):
                        fields[key] = (fileName,
                                       open(filePath, 'rb'),
                                       mimetypes.guess_type(filePath)[0])
                    else:
                        fields[key] = v

            params.update(fields)
            del files, fields
        if self._cert_file:
            cert = (self._cert_file, self._key_file)
        else:
            cert = None   
        if json_encode:
            for k,v in params.items():
                if isinstance(v, (dict, list, tuple, bool)):
                    params[k] = json.dumps(v)
                elif isinstance(v, PropertyMap):
                    params[k] = json.dumps(dict(v))
                elif isinstance(v, InsensitiveDict):
                    params[k] = v.json
        if post_json:  # edge case workflow
            resp = self._session.put(url=url,
                                      json=params,
                                      cert=cert,
                                      files=files)
        else:
            resp = self._session.put(url=url,
                                     data=params,
                                     cert=cert,
                                     files=files)        
        #
        return self._handle_response(resp=resp,
                                         out_path=out_path,
                                         file_name=file_name,
                                         try_json=try_json,
                                         force_bytes=kwargs.pop('force_bytes', False))
    #----------------------------------------------------------------------
    def delete(self, url, params=None, **kwargs):
        """
        sends a PUT request

        ===========================   =====================================================
        **Parameters**                **Description**
        ---------------------------   -----------------------------------------------------
        url                           Optional String. The web endpoint.
        ---------------------------   -----------------------------------------------------
        params                        Optional dict. The Key/value pairs to send along with the request.
        ===========================   =====================================================

        ===========================   =====================================================
        **Optional Parameters**       **Description**
        ---------------------------   -----------------------------------------------------
        add_token                     optional boolean.  True means try to add the boolean,
                                      else do not add a ?token=<foo> to the call.
        ---------------------------   -----------------------------------------------------
        token_as_header               Optional boolean.  If True, the token will go into
                                      the header as `Authorization` header.  This can be
                                      overwritten using the `token_header` parameter
        ---------------------------   -----------------------------------------------------
        token_header                  Optional String. If provided and token_as_header is
                                      True, authentication token will be placed in this
                                      instead on URL string.
        ---------------------------   -----------------------------------------------------
        try_json                      optional boolean.  If true, the call adds the ?f=json.
        ---------------------------   -----------------------------------------------------
        ssl                           optional boolean. If true all calls are forced to be
                                      https.
        ===========================   =====================================================

        :returns: dict or string depending on the response
        """
        out_path = kwargs.pop('out_path', None)
        file_name = kwargs.pop('file_name', None)
        token_as_header = kwargs.pop('token_as_header', True)
        token_header = kwargs.pop('token_header', "X-Esri-Authorization")
        if params is None:
            params = {}
        if self._session is None:
            self._create_session()
        try_json = kwargs.pop("try_json", True)
        add_token = kwargs.pop('add_token', True)
        if url.find('://') == -1:
            url = self._baseurl + url
        if kwargs.pop("ssl", False):
            url = url.replace("http://", "https://")
        if add_token:
            if token_as_header == False and not 'token' in kwargs: #as ?token=
                params['token'] = self.token
            elif token_as_header == False and 'token' in kwargs: #as ?token= and user provides the token
                params['token'] = kwargs['token']
            elif token_as_header and 'token' in kwargs: # as X-Esri-Auth header with given token
                self._session.headers.update({token_header: "Bearer %s" % kwargs['token']})
            elif token_as_header and token_header and self.token: # as X-Esri-Auth header with generated token
                self._session.headers.update({token_header: "Bearer %s" % self.token})

        if try_json:
            params['f'] = 'json'

        resp = self._session.delete(url=url, data=params)
        return self._handle_response(resp=resp,
                                     out_path=out_path,
                                     file_name=file_name,
                                     try_json=try_json,
                                     force_bytes=kwargs.pop('force_bytes', False))
    #----------------------------------------------------------------------
    def streaming_method(self, url, callback,
                         data=None, json_data=None, verb="GET",
                         **kwargs):
        """
        Performs streaming web requests.

        =======================     ===========================================================
        **Parameters**              **Description**
        -----------------------     -----------------------------------------------------------
        url                         Required String. The web resource location.
        -----------------------     -----------------------------------------------------------
        callback                    Required Method.  The callback function to handle the response from the streaming request.

                                    **Example**
                                    ```
                                    def hook(r, *args, **kwargs):
                                        print('called a hook')
                                        return r
                                    ```
                                    See: https://requests.kennethreitz.org/en/master/user/advanced/#event-hooks


        -----------------------     -----------------------------------------------------------
        data                        Optional Dict. The parameters to pass to the method.
        -----------------------     -----------------------------------------------------------
        json_data                   Optional Dict. The parameters to pass to the method. This applies to POST only
        -----------------------     -----------------------------------------------------------
        verb                        Optional String.  The default is GET.  The allowed values are POST, GET, or PUT.
        -----------------------     -----------------------------------------------------------
        kwargs                      Optional Dict.  See https://requests.readthedocs.io/en/master/user/advanced/#request-and-response-objects
        =======================     ===========================================================

        """
        verbs = ['post', 'put', 'get']
        if verb.lower() in verbs:
            hooks = {'response' : callback}
            fn = getattr(self._session, verb.lower())
            if verb.lower() == "post":
                return fn(url=url,
                          data=data,
                          json_data=json,
                          hooks=hooks,
                          stream=True,
                          **kwargs)
            else:
                return fn(url=url,
                          data=data,
                          hooks=hooks,
                          stream=True,
                          **kwargs)
        else:
            allowed_verb = ",".join(verbs)
            raise ValueError(f"Invalid web method only {allowed_verb} as allowed")
        self._session.post(url=url, data=data, json_data=json, stream=True)
    #----------------------------------------------------------------------
    def login(self, username, password, expiration=None):
        """allows a user to login to a site with different credentials"""
        if expiration is None:
            expiration = 1440
        try:
            if self._username != username and \
               self._password != password:
                c = Connection(baseurl=self._baseurl,
                               username=username,
                               password=password)
                self = c
        except:
            raise Exception("Could not create a new login.")
    #----------------------------------------------------------------------
    def relogin(self, expiration=None):
        """ Re-authenticates with the portal using the same username/password. """
        if expiration is None:
            expiration = self._expiration
        self.logout()
        return self.token
    #----------------------------------------------------------------------
    def logout(self):
        """ Logs out of the portal. """
        self._token = None
        self._create_time = None
    #----------------------------------------------------------------------
    @property
    def is_logged_in(self):
        """ Returns true if logged into the portal. """
        return (self._auth in ['ANON', 'UNKNOWN']) == False
    #----------------------------------------------------------------------
    @property
    def product(self):
        """ Returns true if logged into the portal. """
        return self._product
    #----------------------------------------------------------------------
    @property
    def token(self):
        """Gets a Token"""
        if str(self._auth).lower() in ['builtin', 'oauth']:
            if self._expiration is None or self._expiration <= 5:
                self._expiration = 6
            if self._create_time and \
               (datetime.datetime.now() < self._create_time + datetime.timedelta(minutes=self._expiration)):
                return self._token
            elif str(self._auth).lower() == "oauth":
                self._create_time = datetime.datetime.now()
                self._token = self._oauth_token()
                return self._token
            elif self._create_time is None and \
               self._auth == "BUILTIN" and \
               self._product in ['SERVER', 'FEDERATED_SERVER']:
                self._token = self._server_token()
                return self._token
            elif self._product in ['FEDERATED_SERVER', 'GEOEVENT'] and \
                 self._portal_connection:
                self._token = self._server_token()
                return self._token
            elif self._create_time is None and \
                 self._auth == "OAUTH":
                self._token = self._oauth_token()
                return self._token
            elif self._create_time is None and \
                 self._product in ['PORTAL', "AGOL",
                                   "AGO", "ENTERPRISE"] and \
                 self._auth == "BUILTIN":
                self._token = self._enterprise_token()
                return self._token
            else:
                self._create_time = None
                return self.token
        elif str(self._auth.lower()) in ['home']:
            if self._create_time is None:
                self._create_time = datetime.datetime.now()
            if self._expiration is None:
                self._expiration = 1440
            if datetime.datetime.now() >= self._create_time + datetime.timedelta(minutes=self._expiration):
                raise Exception("Token is Expired. Please relogin to portal")
            return self._token
        elif str(self._auth).lower() == 'pro' or\
             self._baseurl.lower() == 'pro':
            self._create_time = datetime.datetime.now()
            self._token = self._pro_token()
            return self._token
        return None
    #----------------------------------------------------------------------
    @token.setter
    def token(self, value):
        """gets/sets the token"""
        if self._token != value:
            self._token = value
    #----------------------------------------------------------------------
    def _pro_token(self):
        """gets the token for various products"""
        if self._auth.lower() == 'pro' and \
           HASARCPY:
            resp = arcpy.GetSigninToken()
            if resp:
                if 'referer' in resp:
                    self._referer = resp['referer']
                if self._session:
                    self._session.headers['Referer'] = self._referer
                else:
                    self._referer = resp['referer']
                    self._session = self._create_session()
                if 'token' in resp:
                    return resp['token']
            else:
                raise Exception(("Could not login using Pro authencation."
                                 "Please verify in Pro that you are logged in."))
        return
    #----------------------------------------------------------------------
    def _server_token(self):
        """generates a server token"""
        if self._token_url is None and \
           self._portal_connection is None and \
           self._product in ['SERVER', "FEDERATED_SERVER"]:
            parsed = urlparse(self.baseurl)
            b = parsed.netloc
            if len(parsed.path) > 1:
                wa = parsed.path[1:].split('/')[0]
            else:
                wa = 'arcgis'
            ep = "admin/generateToken"
            self._token_url = "https://%s/%s/%s" % (b, wa, ep)
        elif self._token_url is None and \
             self._portal_connection:
            token_url = self._portal_connection._baseurl
            p = urlparse(token_url)
            self._token_url = "%s://%s/%s/sharing/rest/generateToken" % ("https",
                                                                         p.netloc,
                                                                         p.path[1:].split('/')[0],)
        if self._portal_connection:
            #self._token_url = token_url
            if self._portal_connection._auth.lower() == 'home':
                self._referer = ""
            ptoken = self._portal_connection.token
            postdata = {'serverURL':self._baseurl,
                        'token': ptoken,
                        'expiration': str(self._expiration),
                        'f': 'json',
                        'request':'getToken',
                        'referer':self._referer}
        else:
            postdata = { 'username': self._username,
                         'password': self._password,
                         #'client': 'requestip',
                         'referer' : self._referer,
                         'expiration': self._expiration,
                         'f': 'json' }
        res = self.post(path=self._token_url,
                        params=postdata,
                        ssl=True,
                        add_token=False)
        if 'error' in res:
            raise Exception(res['error'])
        self._create_time = datetime.datetime.fromtimestamp(int(res['expires'])/1000) - datetime.timedelta(minutes=self._expiration)
        self._token = res['token']
        return res['token']
    #----------------------------------------------------------------------
    def _enterprise_token(self):
        """generates a portal/agol token"""
        if self._referer is None and self._portal_connection is None:
            self._referer = "http"
        elif self._referer is None and self._portal_connection and \
             self._portal_connection._auth.lower() == "home":
            self._referer = ""
        postdata = { 'username': self._username, 'password': self._password,
                     'client': 'referer', 'referer': self._referer,
                     'expiration': self._expiration, 'f': 'json' }
        if self._token_url is None:
            if self._product in ['AGO', 'AGOL']:
                self._token_url = "https://%s/sharing/rest/generateToken" % urlparse(self._baseurl).netloc
            else:
                parsed = urlparse(self._baseurl)
                path = parsed.path
                if path.startswith('/'):
                    path = path[1:]
                self._token_url = "https://%s/%s/sharing/rest/generateToken" % (parsed.netloc,
                                                                                path.split('/')[0])

        res = self.post(path=self._token_url,
                        params=postdata,
                        add_token=False)
        if 'error' in res:
            raise Exception(res['error'])
        self._create_time = datetime.datetime.fromtimestamp(res['expires']/1000) - datetime.timedelta(minutes=self._expiration)
        return res['token']
    #----------------------------------------------------------------------
    def _oauth_token(self):
        """performs the oauth2 when secret and client exist"""
        from oauthlib.oauth2 import BackendApplicationClient
        from requests_oauthlib import OAuth2Session
        auth_url = '%soauth2/authorize' % self.baseurl
        tu = '%soauth2/token' % self.baseurl
        # handles the refreshing of the token
        if not (self._create_time is None) and \
           (datetime.datetime.now() >= self._create_time + datetime.timedelta(minutes=self._expiration)):
            self._token = None
        elif not (self._create_time is None) and \
             not (self._token is None) and \
             (datetime.datetime.now() < self._create_time + datetime.timedelta(minutes=self._expiration)):
            return self._token
        # Handles token generation
        if self._refresh_token is not None and \
           self._client_id is not None and\
           self._token is None: # Case 1: Refreshing a token
            parameters = {
                'client_id': self._client_id,
                'grant_type': 'refresh_token',
                'refresh_token': self._refresh_token,
                'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
            }
            token_info = self.post('oauth2/token', parameters, ssl=True, add_token=False)
            self._token = token_info['access_token']
            return self._token
        elif self._client_id and self._client_secret: # case 2: has both client and secret keys
            client = BackendApplicationClient(client_id=self._client_id)
            oauth = OAuth2Session(client=client)
            res = oauth.fetch_token(
                token_url=self._token_url,
                client_id=self._client_id,
                client_secret=self._client_secret,
                include_client_id=True,
            )
            if 'expires_in' in res:
                self._create_time = datetime.datetime.fromtimestamp(res['expires_at']) - datetime.timedelta(seconds=7200)
                self._expiration = res['expires_in'] / 60
                if 'token' in res:
                    return res['token']
                if 'access_token' in res:
                    return res['access_token']
        elif self._client_id and \
             self._username is None and \
             self._password is None: # case 3: client id only

            auth_url = '%s/oauth2/authorize' % self.baseurl
            tu = '%s/oauth2/token' % self.baseurl
            oauth = OAuth2Session(self._client_id,
                                  redirect_uri="urn:ietf:wg:oauth:2.0:oob")
            authorization_url, state = oauth.authorization_url(auth_url)
            print("Please sign in to your GIS and paste the code that is obtained below.")
            print("If a web browser does not automatically open, please navigate to the URL below yourself instead.")
            print("Opening web browser to navigate to: " + authorization_url)
            import webbrowser, getpass
            webbrowser.open_new(authorization_url)
            authorization_response = getpass.getpass("Enter code obtained on signing in using SAML: ")

            self._create_time = datetime.datetime.now()
            token_info = oauth.fetch_token(tu,
                                           code=authorization_response,
                                           verify=False,
                                           include_client_id=True,
                                           authorization_response='authorization_code')
            self._expiration = token_info['expires_in']/60 - 2
            self._refresh_token = token_info['refresh_token']
            self._token = token_info['access_token']
            return self._token
        elif self._client_id and \
             not (self._username is None and \
                  self._password is None): # case 4: client id and username/password (SAML workflow)
            import re
            import json
            from bs4 import BeautifulSoup

            parameters = {
                'client_id': self._client_id,
                'response_type': 'code',
                'expiration': -1, # we want refresh_token to work for the life of the script
                'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
            }
            content = self.get('oauth2/authorize', parameters, ssl=True, try_json=False, add_token=False)

            pattern = re.compile('var oAuthInfo = ({.*?});', re.DOTALL)
            if len(pattern.findall(content)) == 0:
                pattern = re.compile('var oAuthInfo = ({.*?})', re.DOTALL)
            soup = BeautifulSoup(content, 'html.parser')
            for script in soup.find_all('script'):
                script_code = str(script.string).strip()
                matches = pattern.search(script_code)
                if not matches is None:
                    js_object = matches.groups()[0]
                    try:
                        oauth_info = json.loads(js_object)
                    except:
                        oauth_info = json.loads(js_object + "}")
                    break

            parameters = {
                'user_orgkey': '',
                'username': self._username,
                'password': self._password,
                'oauth_state': oauth_info['oauth_state']
            }
            content = self.post('oauth2/signin', parameters, ssl=True, try_json=False, add_token=False)
            soup = BeautifulSoup(content, 'html.parser')

            if soup.title is not None:
                if 'SUCCESS' in soup.title.string:
                    code = soup.title.string[len('SUCCESS code='):]

            oauth = OAuth2Session(self._client_id,
                                  redirect_uri="urn:ietf:wg:oauth:2.0:oob")
            if code is None:
                raise Exception("Could not generate a token.")
            self._create_time = datetime.datetime.now()
            token_info = oauth.fetch_token(tu, code=code, verify=False, include_client_id=True,
                                           authorization_response='authorization_code')
            self._refresh_token = token_info['refresh_token']
            self._token = token_info['access_token']
            self._expiration = token_info['expires_in']/60 - 2

            return self._token
        return None
    #----------------------------------------------------------------------
    def generate_portal_server_token(self, serverUrl, expiration=1440):
        """generates a server token using Portal token"""
        if self._auth.lower() == "pki":
            from urllib.parse import unquote
            cookies = self._session.cookies.get_dict()
            for key, cookie in cookies.items():
                if key.lower() == "esri_auth":
                    auth = json.loads(unquote(cookie))
                    if 'token' in auth:
                        token = auth['token']
                        break
                del cookie
        else:
            token = self.token
        postdata = {'serverURL':serverUrl,
                    'token': token,
                    'expiration':str(expiration),
                    'f': 'json',
                    'request':'getToken',
                    'referer':self._referer}
        if self._token_url is None:
            if self.baseurl.endswith('/'):
                resp = self.post('generateToken', postdata,
                                 ssl=True, add_token=False)
            else:
                resp = self.post('/generateToken', postdata,
                                 ssl=True, add_token=False)
        else:
            resp = self.post(path=self._token_url, postdata=postdata,
                             ssl=True, add_token=False)
        if isinstance(resp, dict) and resp:
            return resp.get('token')
        else:
            raise Exception(f"Could not generate the token for the service. \n Error Message: \n {resp}")
    #----------------------------------------------------------------------
    def _oauth_authenticate(self):
        """performs oauth check when only client_id is provided"""
        from urllib.parse import urlencode
        client_id = self._client_id
        expiration = self._expiration or 1440
        parameters = {
            'client_id': client_id,
            'response_type': 'code',
            'expiration': -1, # we want refresh_token to work for the life of the script
            'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
        }

        code = None

        if self._username is not None and self._password is not None: # built-in user through OAUTH
            content = self.get(self._token_url,
                               parameters,
                               ssl=True, try_json=False, add_token=False)
            import re
            import json
            from bs4 import BeautifulSoup
            pattern = re.compile('var oAuthInfo = ({.*?});', re.DOTALL)
            soup = BeautifulSoup(content, 'html.parser')
            for script in soup.find_all('script'):
                script_code = str(script.string).strip()
                matches = pattern.search(script_code)
                if not matches is None:
                    js_object = matches.groups()[0]
                    oauth_info = json.loads(js_object)
                    break

            parameters = {
                'user_orgkey': '',
                'username': self._username,
                'password': self._password,
                'oauth_state': oauth_info['oauth_state']
            }
            content = self.post('oauth2/signin', parameters, ssl=True, try_json=False, add_token=False)
            soup = BeautifulSoup(content, 'html.parser')

            if soup.title is not None:
                if 'SUCCESS' in soup.title.string:
                    code = soup.title.string[len('SUCCESS code='):]

        if code is None: # try interactive signin
            url = self._token_url#self._baseurl + 'oauth2/authorize'
            paramstring = urlencode(parameters)
            codeurl = "{}?{}".format(url, paramstring)

            import webbrowser
            import getpass

            print("Please sign in to your GIS and paste the code that is obtained below.")
            print("If a web browser does not automatically open, please navigate to the URL below yourself instead.")
            print("Opening web browser to navigate to: " + codeurl)
            webbrowser.open_new(codeurl)
            code = getpass.getpass("Enter code obtained on signing in using SAML: ")

        if code is not None:
            parameters = {
                'client_id': client_id,
                'grant_type': 'authorization_code',
                'code': code,
                'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
            }
            token_info = self.post(self._token_url, parameters, ssl=True, add_token=False)
            # print('******' + str(token_info))

            self._refresh_token = token_info['refresh_token']
            self._token = token_info['access_token']

            return self._token
        else:
            print("Unable to sign in using OAUTH")
            return None

    #----------------------------------------------------------------------
    def _check_product(self):
        """
        determines if the product is portal, arcgis online or arcgis server
        """
        from urllib.error import HTTPError
        baseurl = self._baseurl
        if self._product == "SERVER":
            if self._token_url is None:
                parsed = urlparse(self._baseurl)
                path = parsed.path[1:].split('/')[0]
                self._token_url = "https://%s/%s/admin/generateToken" % (parsed.netloc,
                                                                         path)

            return "SERVER"
        if baseurl is None:
            return "UNKNOWN"
        if baseurl.lower().find("arcgis.com") > -1:
            parsed = urlparse(self._baseurl)
            self._token_url = "https://%s/sharing/rest/generateToken" % parsed.netloc
            return "AGOL"
        elif baseurl.lower().find("/sharing/rest") > -1:
            if baseurl.endswith("/"):
                res = self.get(baseurl + 'info', params={'f' : 'json'}, add_token=False)
            else:
                res = self.get(baseurl + '/info', params={'f' : 'json'}, add_token=False)
            if self._token_url is None and \
               res is not None and \
               isinstance(res, dict) and \
               'authInfo' in res and \
               'tokenServicesUrl' in res['authInfo'] and \
               res['authInfo']['isTokenBasedSecurity']:
                parsed_from_system = urlparse(res['authInfo']['tokenServicesUrl'])
                parsed = urlparse(baseurl)
                if parsed.netloc.lower() != parsed_from_system.netloc.lower() and \
                   parsed.netloc.find(":7443") > -1: # WA not being used for token url
                    self._token_url = os.path.join(
                        parsed_from_system.scheme + "://",
                        parsed.netloc  + "/arcgis/" + "/".join(parsed_from_system.path[1:].split("/")[1:])
                    )
                    url_test = self._session.post(self._token_url, {'f': 'json'}, allow_redirects=False)
                    if url_test.status_code == 301:
                        self._token_url = url_test.headers['location']
                        if self._baseurl != os.path.dirname(url_test.headers['location']):
                            self._baseurl = os.path.dirname(url_test.headers['location'])
                else:
                    self._token_url = res['authInfo']['tokenServicesUrl']
            elif self._token_url is None and \
                 res is not None and \
                 isinstance(res, dict) and \
                 'authInfo' in res and \
                 'tokenServicesUrl' in res['authInfo'] and \
                 res['authInfo']['isTokenBasedSecurity'] == False:
                self._token_url = None
                self._auth = "OTHER"
            return "PORTAL"
        else:
            #Brute Force Method
            root = baseurl.lower().split("/sharing")[0]
            root = baseurl.lower().split('/rest')[0]
            parts = ['/info', '/rest/info', '/sharing/rest/info']
            params = {"f" : "json"}
            for pt in parts:
                try:
                    #print(pt)
                    res = self.get(root + pt, params=params, add_token=False)
                    if self._token_url is None and \
                       res is not None and \
                       isinstance(res, dict) and \
                       'authInfo' in res and \
                       'tokenServicesUrl' in res['authInfo'] and \
                       res['authInfo']['isTokenBasedSecurity']:
                        self._token_url = res['authInfo']['tokenServicesUrl']
                    elif self._token_url is None and \
                         res is not None and \
                         isinstance(res, dict) and \
                         'authInfo' in res and \
                         'tokenServicesUrl' in res['authInfo'] and \
                         res['authInfo']['isTokenBasedSecurity'] == False:
                        self._token_url = None
                        self._auth = "OTHER"
                except HTTPError as e:
                    res = ""
                except json.decoder.JSONDecodeError:
                    res = ""
                except Exception as e:
                    res = ""
                if isinstance(res, dict) and \
                   "currentVersion" in res and \
                   self._token_url:
                    t_parsed = urlparse(self._token_url[1:]).path
                    b_parsed = urlparse(self._baseurl[1:]).path
                    if t_parsed.startswith("/"):
                        t_parsed = t_parsed[1:].split("/")[0]
                    else:
                        t_parsed = t_parsed.split("/")[0]
                    if b_parsed.startswith("/"):
                        b_parsed = b_parsed[1:].split("/")[0]
                    else:
                        b_parsed = b_parsed.split("/")[0]
                    if t_parsed.lower() != b_parsed.lower():
                        self._token_url = None
                        return "FEDERATED_SERVER"
                    return "SERVER"
                elif isinstance(res, dict) and \
                     "currentVersion" in res and \
                     self._token_url is None:
                    return "SERVER"
                del pt
                del res
        return "PORTAL"

