from requests import RequestException

from CommonServerPython import *

''' IMPORTS '''

import sys
import requests
import urllib3
import json
import time
import re
import urllib.request
import urllib.parse
import urllib.error
from urllib.parse import urlparse
from distutils.util import strtobool
from datetime import datetime, timedelta
from requests.exceptions import HTTPError

# Disable insecure warnings
urllib3.disable_warnings()

''' GLOBALS/PARAMS '''

PARAMS = demisto.params()
API_TOKEN = PARAMS.get('apitoken_creds', {}).get('password') or PARAMS.get('APIToken')
BASE_URL = PARAMS.get('baseURL')
USE_SSL = not PARAMS.get('insecure', False)
DEFAULT_HEADERS = {
    'Authorization': 'Bearer {}'.format(API_TOKEN),
    'Accept': 'application/json'
}
SUSPICIOUS_THRESHOLD = arg_to_number(PARAMS.get('suspicious_threshold', 0)) or 0
MALICIOUS_THRESHOLD = arg_to_number(PARAMS.get('dboscore_threshold', -100))
MAX_THRESHOLD_VALUE = 100
MIN_THRESHOLD_VALUE = -100

reliability = PARAMS.get('integrationReliability')
reliability = reliability if reliability else DBotScoreReliability.B

if DBotScoreReliability.is_valid_type(reliability):
    reliability = DBotScoreReliability.get_dbot_score_reliability_from_str(reliability)
else:
    Exception("Please provide a valid value for the Source Reliability parameter.")

''' MAPS '''

# This object describe the result of the http request of getDomainSecurity function
# each field has Name,Info & ContextKey  - thats just looks scarry
SECURITY_RESULT_INFO = {
    'dga_score': {'Name': 'DGA', 'ContextKey': 'DGA',
                  'Info': 'Domain Generation Algorithm. This score is generated based on the likeliness of the domain '
                          'name being generated by an algorithm rather than a human. This algorithm is designed to '
                          'identify domains which have been created using an automated randomization strategy, '
                          'which is a common evasion technique in malware kits or botnets. This score ranges from '
                          '-100 (suspicious) to 0 (benign)'},
    'perplexity': {'Name': 'Perplexity',
                   'Info': 'A second score on the likeliness of the name to be algorithmically generated, on a scale '
                           'from 0 to 1. This score is to be used in conjunction with DGA'},
    'entropy': {'Name': 'Entropy',
                'Info': 'The number of bits required to encode the domain name, as a score. This score is to be used '
                        'in conjunction with DGA and Perplexity'},
    'securerank2': {'Name': 'SecureRank',
                    'Info': 'Suspicious rank for a domain that reviews based on the lookup behavior of client IP for '
                            'the domain. Securerank is designed to identify hostnames requested by known infected '
                            'clients but never requested by clean clients, assuming these domains are more likely to '
                            'be bad. Scores returned range from -100 (suspicious) to 100 (benign)'},
    'pagerank': {'Name': 'PageRank', 'Info': 'Popularity according to Google\'s pagerank algorithm'},
    'asn_score': {'Name': 'ASN Score', 'ContextKey': 'ASNScore',
                  'Info': 'ASN reputation score, ranges from -100 to 0 with -100 being very suspicious'},
    'prefix_score': {'Name': 'Prefix Score', 'ContextKey': 'PrefixScore',
                     'Info': 'Prefix ranks domains given their IP prefixes (an IP prefix is the first three octets in '
                             'an IP address) and the reputation score of these prefixes. Ranges from -100 to 0, '
                             '-100 being very suspicious'},
    'rip_score': {'Name': 'RIP Score', 'ContextKey': 'RIPScore',
                  'Info': 'RIP ranks domains given their IP addresses and the reputation score of these IP addresses. '
                          'Ranges from -100 to 0, -100 being very suspicious'},
    'popularity': {'Name': 'Popularity',
                   'Info': 'The number of unique client IPs visiting this site, relative to the all requests to all '
                           'sites. A score of how many different client/unique IPs go to this domain compared to '
                           'others'},
    'geoscore': {'Name': 'GeoScore',
                 'Info': 'A score that represents how far the different physical locations serving this name are from '
                         'each other'},
    'ks_test': {'Name': 'Kolmogorov-Smirnov Test', 'ContextKey': 'KolmogorovSmirnovTest',
                'Info': "Kolmogorov-Smirnov test on geodiversity. 0 means that the client traffic matches what is "
                        "expected for this TLD"},
    'attack': {'Name': 'Attack Name', 'ContextKey': 'AttackName',
               'Info': 'The name of any known attacks associated with this domain. Returns blank if no known threat '
                       'associated with domain'},
    'threat_type': {'Name': 'Threat Type', 'ContextKey': 'ThreatType',
                    'Info': 'The type of the known attack, such as botnet or APT. Returns blank if no known threat '
                            'associated with domain'}
}

# used to describe result on getDomainDNSHistory function
IP_DNS_FEATURE_INFO = {
    'rr_count': 'Number of records of that type mapping to the given IP',
    'ld2_count': 'Number of 2-level names mapping to the given IP',
    'ld3_count': 'Number of 3-level names mapping to the given IP',
    'ld2_1_count': 'Number of 2-level names, without the TLD, mapping to the given IP',
    'ld2_2_count': 'Number of 3-level names, without the TLD, mapping to a given IP',
    'div_ld2': 'ld2_count divided by the number of records',
    'div_ld3': 'ld3_count divided by the number of records',
    'div_ld2_1': 'ld2_1_count divided by the number of records',
    'div_ld2_2': 'ld2_2_count divided by the number of record'
}

''' HELPER FUNCTIONS '''


def verify_threshold_params(suspicious_threshold, malicious_threshold):
    if not (MAX_THRESHOLD_VALUE >= suspicious_threshold > malicious_threshold >= MIN_THRESHOLD_VALUE):  # type: ignore
        return_error(
            "Please provide valid threshold values for the Suspicious and Malicious thresholds when Suspicious is greater than "
            f"Malicious and both are within a range of {MIN_THRESHOLD_VALUE} to {MAX_THRESHOLD_VALUE}"
        )


def extract_domain_name(url):
    return url.split("//")[-1].split("/")[0]


def http_request(api_endpoint, params_dict=None, method='GET', data_list=None):
    req_params = {}  # type: dict
    # request does not accept lists, only string/dict
    if data_list and isinstance(data_list, list):
        data_list = json.dumps(data_list)
    if params_dict:
        req_params.update(params_dict)
    url = BASE_URL + api_endpoint
    LOG('running %s request with url=%s\tparams=%s\tdata=%s' % (method, url, json.dumps(req_params), data_list))
    try:
        res = requests.request(
            method,
            url,
            verify=USE_SSL,
            params=req_params,
            headers=DEFAULT_HEADERS,
            data=data_list
        )

        res.raise_for_status()
        return res.json()

    except Exception as e:
        LOG(e)
        raise


def format_string_to_table_header_format(string):
    # example: "one_two" to "One Two"
    if type(string) in STRING_TYPES:
        return " ".join(word.capitalize() for word in string.replace("_", " ").split())
    else:
        return_error('The key is not a string: {}'.format(string))


def format_string_to_context_key_format(string):
    # example: "one_two" to "OneTwo"
    if type(string) in STRING_TYPES:
        return "".join(word.capitalize() for word in string.split('_'))
    else:
        return_error('The key is not a string: {}'.format(string))


def date_to_timestamp_func(date):
    # this helper function tries to parse a date time string according to a specific format
    # if it fails, it will just output the original value
    try:
        ts = datetime.strptime(date[0:16], '%Y-%m-%dT%H:%M')
    except ValueError:
        pass
    else:
        if date[19] == '+':
            ts += timedelta(hours=int(date[20:22]), minutes=int(date[22:24]))
        elif date[19] == '-':
            ts -= timedelta(hours=int(date[20:22]), minutes=int(date[22:24]))
        ts = time.mktime(ts.timetuple())  # type: ignore
        return str(int(ts) * 1000)  # type: ignore
    return date


def timestamp_to_date(ts):
    if ts:
        # Gets a timestamp (either str or int, either in seconds or milliseconds) and converts it to a date.
        ts = str(ts)
        if len(ts) > 10:
            ts = ts[:10]
        ts = int(ts)
        return datetime.utcfromtimestamp(ts).strftime('%Y-%m-%dT%H:%M:%S')
    return ts


def calculate_domain_dbot_score(status: int | None, securerank2: int | None) -> int:
    match status:
        case -1:
            return Common.DBotScore.BAD
        case 1:
            return Common.DBotScore.GOOD
        case 0 | None:
            # in these cases, security_rank2 is used
            if securerank2 is None:
                return Common.DBotScore.NONE

            if (threshold := demisto.args().get('threshold', MALICIOUS_THRESHOLD)) is None  \
                    or (malicious_threshold := arg_to_number(threshold)) is None:
                raise RuntimeError(f"Cannot convert {threshold=} to number")

            if securerank2 < malicious_threshold:
                return Common.DBotScore.BAD

            if securerank2 < SUSPICIOUS_THRESHOLD:
                return Common.DBotScore.SUSPICIOUS

            return Common.DBotScore.GOOD

        case _:
            raise ValueError(f"unexpected {status=}, expected 0,1 or -1")


''' INTERNAL FUNCTIONS '''


def get_co_occurences(domain):
    # Build & Send request
    endpoint_url = '/recommendations/name/' + domain + '.json'
    res_co_occurences = http_request(endpoint_url)

    # Assign and validate response
    co_occurences = res_co_occurences.get('pfs2', [])
    if not res_co_occurences['found'] or not co_occurences:
        return False
    table_co_occurences = []

    for co_occurence in co_occurences:
        table_co_occurences.append({
            'Name': co_occurence[0],
            'Score': co_occurence[1]
        })

    return table_co_occurences


def get_domains_categorization(domains):
    # Build & Send request
    endpoint_url = '/domains/categorization?showLabels'
    res = http_request(endpoint_url, None, 'POST', domains)
    # Validate response
    if not res:
        return False
    return res


''' BUSINESS LOGIC / COMMANDS '''


def get_domain_categorization_command():
    # Initialize
    contents = []  # type: ignore
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    categorization = get_domain_categorization(domain)
    if categorization:
        # Process response - build context and markdown table
        domain_context = {
            'Name': domain
        }

        contents = {  # type: ignore
            # Will be override in case result contains any
            'Content Categories': 'No Content Categories Were Found',
            'Malware Categories': 'No Security Categories Were Found'
        }

        if categorization:
            if categorization.get('status'):
                contents['Status'] = categorization['status']  # type: ignore
        if categorization.get('content_categories'):
            content_categories = ",".join(categorization['content_categories'])
            contents['Content Categories'] = content_categories  # type: ignore
            domain_context['ContentCategories'] = content_categories
        if categorization.get('security_categories'):
            security_categories = ",".join(categorization['security_categories'])
            contents['Malware Categories'] = security_categories  # type: ignore
            domain_context['SecurityCategories'] = security_categories
        if categorization['status'] == -1:
            domain_context['Malicious'] = {
                'Vendor': 'Cisco Umbrella Investigate',
                'Description': security_categories
            }

        context[outputPaths['domain']] = domain_context

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Categorization:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_categorization(domain):
    # Build & Send request
    endpoint_url = '/domains/categorization/' + domain + '?showLabels'
    res = http_request(endpoint_url)

    # Validate and assign response
    categorization = res.get(domain, [])
    if not categorization:
        return False
    return categorization


def get_domain_search_command():
    # Initialize
    contents = []  # type: ignore
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    regex = demisto.args()['regex']
    start = demisto.args().get('start', '')
    limit = int(demisto.args().get('limit'))
    # Fetch data
    matches = get_domain_search(regex, start)
    if matches:
        # Process response - build context and markdown table
        if limit:
            matches = matches[:limit]
        contents = matches[:]
        for index, row in enumerate(contents):
            contents[index] = {
                'Name': row['name'],
                'First Seen': row['firstSeenISO'],
                'Security Categories': ",".join(row['securityCategories'])
            }

        domain_context = []
        for match in matches:
            security_categories_str = ",".join(match['securityCategories'])
            domain = {
                'Name': match['name'],
                'SecurityCategories': security_categories_str if security_categories_str else None,
                'FirstSeen': match['firstSeen'],
                'FirstSeenISO': match['firstSeenISO'],
                'FirstSeean': match['firstSeen'],
                'FirstSeeanISO': match['firstSeenISO']
            }
            if 'Malware' in security_categories_str:
                domain['Malicious'] = {
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Description': 'Tagged as malware'
                }
            domain_context.append(domain)

        context[outputPaths['domain']] = domain_context

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Search Results:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_search(regex, start):
    # Build & Send request
    matches = {}  # type: ignore
    start = "".join(start.split()) if start else '-31days'
    endpoint_url = '/search/' + regex
    params = {
        'start': start,
        'includecategory': 'true'
    }
    res = http_request(endpoint_url, params)

    # Validate and assign response
    matches = res.get('matches')
    if not matches or not isinstance(matches, list):
        return False
    return matches


def get_domain_co_occurrences_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    occurrences = get_domain_co_occurrences(domain)
    if occurrences:
        # Process response - build context and markdown table
        for occurrence in occurrences:
            contents.append({
                'Name': occurrence[0],
                'Score': occurrence[1]
            })

        if contents:
            context[outputPaths['domain']] = {
                'Name': domain,
                'CoOccurrences': contents
            }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Co-occurrences:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_co_occurrences(domain):
    # Build & Send request
    endpoint_url = '/recommendations/name/' + domain + '.json'
    res = http_request(endpoint_url)

    # Validate and assign response
    occurrences = res.get('pfs2')
    if not isinstance(occurrences, list) or not occurrences:
        return False
    return occurrences


def get_domain_related_command():
    # Initialize
    contents = []
    context = {}  # type: ignore
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    related_list = get_domain_related(domain)
    if related_list:
        # Process response - build context and markdown table
        for related in related_list:
            contents.append({
                'Name': related[0],
                'Score': related[1]
            })

        context = {}
        if contents:
            context[outputPaths['domain']] = {
                'Name': domain,
                'Related': contents
            }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Related Domains:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_related(domain):
    # Build & Send request
    endpoint_url = '/links/name/' + domain + '.json'
    res = http_request(endpoint_url)

    # Validate and assign response
    related_list = res.get('tb1', [])
    if not isinstance(related_list, list) or not related_list:
        return False
    return related_list


def get_domain_security_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    res = get_domain_security(domain)
    if res:
        # Process response - build context and markdown table
        # each key in SECURITY_RESULT_INFO corrispond to a key in 'res'
        # we get the score from 'res' & add Name & Info from SECURITY_RESULT_INFO
        for key in SECURITY_RESULT_INFO:
            info = SECURITY_RESULT_INFO[key]
            contents.append({
                'Name': info['Name'],
                'Score': res[key],
                'Info': info['Info']
            })

        domain_security_context = {}

        for key in SECURITY_RESULT_INFO:
            context_key = SECURITY_RESULT_INFO[key].get('ContextKey', format_string_to_context_key_format(
                SECURITY_RESULT_INFO[key]['Name']))
            domain_security_context[context_key] = res[key]

        if domain_security_context:
            dbot_score = calculate_domain_dbot_score(status=(get_domain_categorization(domain) or {}).get('status'),
                                                     securerank2=(get_domain_details(domain) or {}).get('securerank2'))
            context[outputPaths['dbotscore']] = {
                'Indicator': domain,
                'Type': 'domain',
                'Vendor': 'Cisco Umbrella Investigate',
                'Score': dbot_score,
                'Reliability': reliability
            }

            context[outputPaths['domain']] = {
                'Name': domain,
                'Security': domain_security_context
            }

            if dbot_score == Common.DBotScore.BAD:
                context[outputPaths['domain']]['Malicious'] = {
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Description': 'Malicious domain found via umbrella-domain-security'
                }
    else:
        context[outputPaths['dbotscore']] = {
            'Indicator': domain,
            'Type': 'domain',
            'Vendor': 'Cisco Umbrella Investigate',
            'Score': Common.DBotScore.NONE,
            'Reliability': reliability

        }
    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Domain Security Info:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_security(domain):
    # Build & Send request
    endpoint_url = '/security/name/' + domain + '.json'
    res = http_request(endpoint_url)

    # Validate and assign response
    if not res or res.get('errorMessage'):
        return False
    return res


def get_domain_dns_history_command():
    # Initialize
    contents = {}  # type: ignore
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    features = get_domain_dns_history(domain)
    if features:
        # Process response - build context and markdown table
        dns_history_context = {}

        for feature in features:
            table_key = format_string_to_table_header_format(feature)
            context_key = format_string_to_context_key_format(feature)
            value = features.get(feature, '')
            dns_history_context[context_key] = value
            if feature in ('locations'):
                contents[table_key] = []
                for location in features[feature]:
                    contents[table_key].append("[ " + str(location['lat']) + ", " + str(location['lon']) + " ]")
                contents[table_key] = ','.join(contents[table_key])
            else:
                if isinstance(value, list):
                    contents[table_key] = ','.join(str(item) for item in value)
                elif value:
                    contents[table_key] = value

        context[outputPaths['domain']] = {
            'Name': domain,
            'DNSHistory': dns_history_context
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('DNS History:', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_dns_history(domain):
    # this command return 2 entries - but the context update is done with the 2nd entry
    # Build & Send request
    endpoint_url = '/dnsdb/name/a/' + domain + '.json'
    res = http_request(endpoint_url)

    # Validate and assign response
    features = res.get('features', {})
    if not features or not features.keys() > {"base_domain", "is_subdomain"}:
        return False
    # this is the actual path for ip address
    address = res.get('rrs_tf')[0].get('rrs')[0].get('rr')
    features['ip'] = address
    return features


def get_ip_dns_history_command():
    # Initialize
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    ip = demisto.args()['ip']
    limit = int(demisto.args().get('limit'))
    # Fetch data
    response_object = get_ip_dns_history(ip)
    response_contents = []
    features_contents = {}

    if response_object:
        response = response_object['response']
        features = response_object['features']
        # Process response - build context and markdown table

        if limit:
            response = response[:limit]
        for item in response:
            response_contents.append({
                'RR': item['rr'],
                'TTL': item['ttl'],
                'Class': item['class'],
                'Type': item['type'],
                'Name': item['name']
            })

        features_context = {}
        for key in IP_DNS_FEATURE_INFO:
            # table_key = format_string_to_table_header_format(key)
            features_contents[IP_DNS_FEATURE_INFO[key]] = features[key]
            context_key = format_string_to_context_key_format(key)
            features_context[context_key] = features[key]

        context[outputPaths['ip']] = {
            'Address': ip,
            'DNSHistory': {
                'RRS': response_contents,
                'Features': features_context
            }
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': [response_contents, features_contents],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('RRS:', response_contents, headers) + tableToMarkdown('Features:',
                                                                                               features_contents,
                                                                                               headers),
        'EntryContext': context
    })

    return results


def get_ip_dns_history(ip):
    # Build & Send request
    endpoint_url = '/dnsdb/ip/a/' + ip + '.json'
    res = http_request(endpoint_url)

    # Validate and assign response
    features = res.get('features', [])
    response = res.get('rrs', [])
    if not features or not response:
        return False
    return {'features': features, 'response': response}


def get_ip_malicious_domains_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    context_dbotscore = []
    # Get vars
    ip = demisto.args()['ip']
    # Fetch data
    res = get_ip_malicious_domains(ip)
    if res:
        # Process response - build context and markdown table
        for domain in res:
            contents.append({
                'Name': domain['name'],
                'Malicious': {
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Description': 'For IP ' + ip
                }
            })
            context_dbotscore.append({
                'Indicator': domain['name'],
                'Type': 'domain',
                'Vendor': 'Cisco Umbrella Investigate',
                'Score': 3,
                'Reliability': reliability
            })

        if contents:
            context[outputPaths['domain']] = contents
            context[outputPaths['dbotscore']] = context_dbotscore

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Malicious Domains:', contents, headers),
        'EntryContext': context
    })

    return results


def get_ip_malicious_domains(ip):
    # Build & Send request
    endpoint_url = '/ips/' + ip + '/latest_domains'
    res = http_request(endpoint_url)

    # Validate and assign response
    if not res:
        return False
    return res


def get_domain_command():
    results = []
    execution_metrics = ExecutionMetrics()
    domains_list = argToList(demisto.args()['domain'])
    for domain in domains_list:
        contents = []
        context = {}
        headers = []  # type: ignore
        domain = extract_domain_name(domain)
        try:
            whois = get_whois_for_domain(domain)
            admin = {
                'Country': whois.get('administrativeContactCountry'),
                'Email': whois.get('administrativeContactEmail'),
                'Name': whois.get('administrativeContactName'),
                'Phone': whois.get('administrativeContactTelephone')
            }
            registrant = {
                'Country': whois.get('registrantCountry'),
                'Email': whois.get('registrantEmail'),
                'Name': whois.get('registrantName'),
                'Phone': whois.get('registrantTelephone')
            }
            first_queried = whois.get('created')
            name_servers = whois.get('nameServers')
            emails = whois.get('emails')
            registrar = {'Name': whois.get('registrarName')}
            creation_date = first_queried
            domain_status = whois.get('status')
            updated_date = whois.get('updated')
            expiration_date = whois.get('expires')

            whois = {
                'Name': whois.get('domainName'),
                'Registrar Name': whois.get('registrarName'),
                'Last Retrieved': whois.get('timeOfLatestRealtimeCheck'),
                'Created': whois.get('created'),
                'Updated': whois.get('updated'),
                'Expires': whois.get('expires'),
                'IANAID': whois.get('registrarIANAID'),
                'Last Observed': whois.get('auditUpdatedDate')
            }

            domain_categorization = []  # type: ignore
            domain_categorization = get_domain_categorization(domain)
            content_categories = domain_categorization.get('content_categories')  # type: ignore
            malware_categories = domain_categorization.get('security_categories')  # type: ignore
            risk_score = domain_categorization.get('status')  # type: ignore
            domain_categorization_table = {
                'Content Categories': content_categories,
                'Malware Categories': malware_categories
            }

            domain_details = get_domain_details(domain) or {}
            popularity = domain_details.get('popularity')  # type: ignore
            secure_rank = domain_details.get('securerank2')  # type: ignore
            dbot_score = calculate_domain_dbot_score(status=risk_score,
                                                     securerank2=secure_rank)
            context[outputPaths['domain']] = {
                'Name': domain,
                'Admin': admin,
                'Registrant': registrant,
                'Registrar': registrar,
                'CreationDate': creation_date,
                'DomainStatus': domain_status,
                'UpdatedDate': updated_date,
                'ExpirationDate': expiration_date,
                'Umbrella': {
                    'RiskScore': risk_score,
                    'SecureRank': secure_rank,
                    'FirstQueriedTime': first_queried,
                    'ContentCategories': content_categories,
                    'MalwareCategories': malware_categories
                }
            }

            # Add malicious if needed
            if dbot_score == Common.DBotScore.BAD:
                context[outputPaths['domain']]['Malicious'] = {
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Description': 'Malicious domain found with risk score -1'
                }

            context[outputPaths['dbotscore']] = {
                'Indicator': domain,
                'Type': 'domain',
                'Vendor': 'Cisco Umbrella Investigate',
                'Score': dbot_score,
                'Reliability': reliability
            }

            contents.append({
                'Risk Score': risk_score,
                'Secure Rank': secure_rank,
                'Populairty': popularity,
                'Demisto Reputation': scoreToReputation(dbot_score),
                'First Queried time': first_queried,
            })

            # Domain reputation + [whois -> whois nameservers -> whois emails] + domain categorization
            readable_domain_reputation = tableToMarkdown('"Umbrella Investigate" Domain Reputation for: ' + domain,
                                                         contents, headers)
            readable_whois = tableToMarkdown('"Umbrella Investigate" WHOIS Record Data for: ' + domain, whois, headers,
                                             date_fields=["Last Retrieved"])
            readable_name_servers = tableToMarkdown('Name Servers:', {'Name Servers': name_servers}, headers)
            readable_emails = tableToMarkdown('Emails:', emails, ['Emails'])
            readable_domain = tableToMarkdown('Domain Categorization:', domain_categorization_table, headers)
            readable = readable_domain_reputation + readable_whois + readable_name_servers + readable_emails + readable_domain

            results.append(CommandResults(
                readable_output=readable,
                outputs=context,
                raw_response=[contents, whois, name_servers, emails, domain_categorization_table]
            ))
            execution_metrics.success += 1
        except RequestException as r:
            if r.response.status_code == 429:
                execution_metrics.quota_error += 1
                results.append(
                    CommandResults(
                        readable_output="Quota exceeded.",
                        outputs=context,
                        raw_response=contents

                    ))
                continue

            execution_metrics.general_error += 1
            if r.response.status_code == 404:
                human_readable = tableToMarkdown(name='Cisco Umbrella Investigate:',
                                                 t={'DOMAIN': domain, 'Result': 'Not found'},
                                                 headers=['DOMAIN', 'Result'])

                context[outputPaths['domain']] = {'Name': domain}
                context[outputPaths['dbotscore']] = {'Indicator': domain,
                                                     'Type': 'domain',
                                                     'Vendor': 'Cisco Umbrella Investigate',
                                                     'Score': 0,
                                                     'Message': 'No results found',
                                                     'Reliability': reliability}

                results.append(
                    CommandResults(
                        readable_output=human_readable,
                        outputs=context,
                        raw_response=contents
                    ))
            else:
                results = append_metrics(execution_metrics, results)
                return_results(results)
                return_error(r.response.text)

    results = append_metrics(execution_metrics, results)
    return results


def get_related_domains_command():
    # Initialize
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    is_co_occurences = bool(strtobool(demisto.args().get('coOccurences', False)))
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    related_domains = get_related_domains(domain, is_co_occurences)
    contents_related_domains = []
    contents_co_occurences = {}  # type: ignore
    co_occurences_md = ''
    if related_domains:
        # Process response - build context and markdown table
        for related_domain in related_domains:
            contents_related_domains.append({
                'Name': related_domain[0],
                'Score': related_domain[1]
            })

        if related_domains:
            context['Umbrella.RelatedDomains(val.Domain && val.Domain == obj.Domain)'] = {
                'Data': contents_related_domains,
                'Domain': domain
            }

        # Create another request in case co_occurences flag is raised, add the results with the main request
        if is_co_occurences:
            contents_co_occurences = get_co_occurences(domain)
            if contents_co_occurences:
                co_occurences_md = tableToMarkdown('"Umbrella Investigate" Domain Co-occurences for: ' + domain,
                                                   contents_co_occurences, headers)
                if related_domains:
                    context['Umbrella.CoOccurences(val.Domain && val.Domain == obj.Domain)'] = {
                        'Domain': domain,
                        'Data': contents_co_occurences
                    }
                else:
                    context['Umbrella.CoOccurences(val.Domain && val.Domain == obj.Domain)'] = {
                        'Data': contents_co_occurences,
                        'Domain': domain
                    }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': [contents_related_domains, contents_co_occurences],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Related Domains for a Domain: ',
                                         contents_related_domains, headers) + co_occurences_md,
        'EntryContext': context
    })

    return results


def get_related_domains(domain, is_co_occurences):
    # Main Request
    # Build & Send request
    endpoint_url = '/links/name/' + domain + '.json'
    res_related_domains = http_request(endpoint_url)

    # Assign and validate response
    related_domains = res_related_domains.get('tb1', [])
    if not related_domains:
        return False
    return related_domains


def get_domain_classifiers_command():
    # Initialize
    contents = {}
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    res = get_domain_classifiers(domain)
    if res:
        # Process response - build context and markdown table
        security_categories = res.get('securityCategories', [])
        attacks = res.get('attacks', [])
        threat_types = res.get('threatTypes', [])
        contents['Security Categories'] = security_categories
        contents['Attacks'] = attacks
        contents['Threat Types'] = threat_types

        if contents:
            context['Umbrella.DomainClassifiers(val.Domain && val.Domain == obj.Domain)'] = {
                'Data': {
                    'MalwareCategories': security_categories,
                    'Attacks': attacks,
                    'ThreatTypes': threat_types
                },
                'Domain': domain
            }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Domain Classifiers: ' + domain, contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_classifiers(domain):
    # Build & Send request
    endpoint_url = '/url/' + domain + '/classifiers'
    res = http_request(endpoint_url)

    # Assign and validate response
    if not res['securityCategories'] and not res['attacks'] and not res['threatTypes']:
        return False
    return res


def get_domain_query_volume_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    queries_context = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    start_date_string = demisto.args()['start']
    stop_date_string = demisto.args()['stop']
    match = demisto.args()['match']
    limit = int(demisto.args().get('limit'))

    # validation and user input conversion
    if match != 'all' and match != 'exact' and match != 'component':
        return_error('Not a valid type. Valid options are all, exact, or component.')

    # Fetch data
    response_object = get_domain_query_volume(domain, start_date_string, stop_date_string, match)
    if response_object:
        dates = response_object.get('dates')
        queries = response_object.get('queries')

        # Process response - build context and markdown table
        start_date = dates[0]
        stop_date = dates[1]
        # Query timestamp/hour needs to be calculated manually, every entry represents 1 hour (3600 secods). ts is in
        # milliseconds
        query_ts = start_date / 1000
        if limit:
            queries = queries[:limit]
        for query in queries:
            contents.append({
                'Queries': query,
                'Query Hour': timestamp_to_date(query_ts)
            })
            queries_context.append({
                'Queries': query,
                'QueryHour': timestamp_to_date(query_ts)
            })
            query_ts = query_ts + 3600

        context['Umbrella.QueryVolume(val.Domain && val.Domain == obj.Domain)'] = {
            'Domain': domain,
            'Data': {
                'StartDate': timestamp_to_date(start_date),
                'StopDate': timestamp_to_date(stop_date),
                'QueriesInfo': queries_context
            }
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown(
            '"Umbrella Investigate" Domain Volume: ' + domain + '\nStart Date ' + timestamp_to_date(
                start_date) + ' - Stop Date ' + timestamp_to_date(stop_date), contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_query_volume(domain, start_date_string, stop_date_string, match):
    # user input conversion
    start_ts = date_to_timestamp_func(start_date_string)
    stop_ts = date_to_timestamp_func(stop_date_string)

    # Build & Send request
    params = {
        'start': start_ts,
        'stop': stop_ts,
        'match': match
    }
    endpoint_url = '/domains/volume/' + domain
    res = http_request(endpoint_url, params)

    # Assign and validate response
    dates = res.get('dates', [])
    queries = res.get('queries', [])
    if not dates or not queries:
        return False
    return {'dates': dates, 'queries': queries}


def get_domain_details_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    # Fetch data
    res = get_domain_details(domain)
    if res:
        # Process response - build context and markdown table
        # each key in SECURITY_RESULT_INFO corrispond to a key in 'res'
        # we get the score from 'res' & add Name & Info from SECURITY_RESULT_INFO
        for key in SECURITY_RESULT_INFO:
            info = SECURITY_RESULT_INFO[key]
            contents.append({
                'Score': res[key],
                'Name': info['Name'],
                'Info': info['Info']
            })

        domain_security_context = {}

        for key in SECURITY_RESULT_INFO:
            context_key = SECURITY_RESULT_INFO[key].get('ContextKey', format_string_to_context_key_format(
                SECURITY_RESULT_INFO[key]['Name']))
            domain_security_context[context_key] = res[key]

        if domain_security_context:
            context['Umbrella.DomainDetails(val.Domain && val.Domain == obj.Domain)'] = {
                'Domain': domain,
                'Data': domain_security_context
            }
            dbot_score = calculate_domain_dbot_score(status=(get_domain_categorization(domain) or {}).get('status'),
                                                     securerank2=res.get('securerank2'))
            context[outputPaths['dbotscore']] = {
                'Indicator': domain,
                'Type': 'domain',
                'Vendor': 'Cisco Umbrella Investigate',
                'Score': dbot_score,
                'Reliability': reliability
            }
            if dbot_score == Common.DBotScore.BAD:
                context[outputPaths['domain']] = {}
                context[outputPaths['domain']]['Malicious'] = {
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Description': 'Malicious domain found via get-domain-details'
                }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Domain Reputation: ' + domain, contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_details(domain):
    # Build & Send request
    endpoint_url = '/security/name/' + domain
    res = http_request(endpoint_url)

    # Assign and validate response
    if not res:
        return False
    return res


def get_domains_for_email_registrar_command():
    # Initialize
    contents = []  # type: ignore
    context = {}
    headers = []  # type: ignore
    results = []
    markdown = ''
    # Get vars
    emails = argToList(demisto.args()['emails'])
    offset = demisto.args().get('offset', '')
    sort = demisto.args().get('sort', '')
    limit = demisto.args().get('limit', '')
    # user input validation
    if not isinstance(emails, list):
        return_error('Emails list is not formatted correctly, please try again.')
    if sort:
        if sort != 'created' and sort != 'updated':
            return_error('The parameter sort accept only these values: created/updated/expired.')
    for email in emails:
        if not re.match(emailRegex, email):
            return_error('The provided email is not valid: ' + email)
    # Fetch data
    res = get_domains_for_email_registrar(emails, offset, sort, limit)
    if res:
        # Process response - build context and markdown table
        domains = []  # type: ignore
        for email in emails:
            domains_contents = []
            emails_contents = []  # type: ignore
            domains_list = []
            emails_context = []
            # get the entry that matches the provided emails each time
            email_res = res[email]
            domains = email_res.get('domains', [])
            if not email_res or not domains:
                continue

            # going over all the domains associated with this email, making POST request to get each categorization
            for domain in domains:
                domains_list.append(domain['domain'])
            domains_info = get_domains_categorization(domains_list)
            if domains_info:
                for domain in domains:
                    domains_contents.append({
                        'Name': domain['domain'],
                        'Security Categories': domains_info[domain['domain']]['security_categories'],
                        'Content Categories': domains_info[domain['domain']]['content_categories'],
                        'Is Current': domain['current']
                    })

            # each email has its own data + associated domains attached
            emails_context.append({
                'TotalResults': email_res['totalResults'],
                'MoreDataAvailable': email_res['moreDataAvailable'],
                'ResultLimit': email_res['limit'],
                'Domains': domains_contents
            })
            # each email represented by 2 tables
            # Build Output
            markdown = markdown + tableToMarkdown('Domains Associated with: ' + email, domains_contents, headers)
            contents.extend((emails_contents, domains_contents))

        context['Umbrella.AssociatedDomains(val.Email && val.Email == obj.Email)'] = {
            'Email': email,
            'Data': emails_context
        }

    if not markdown:
        markdown = tableToMarkdown('Domains Associated with: ' + email, domains_contents, headers)

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': markdown,
        'EntryContext': context
    })

    return results


def get_domains_for_email_registrar(emails, offset, sort, limit):
    # Build & Send request
    params = {}  # type: ignore
    # single email has different api call over multiple emails
    if len(emails) == 1:
        endpoint_url = '/whois/emails/' + emails[0]
        if sort or limit or offset:
            params = {
                'sortField': sort,
                'limit': limit,
                'offset': offset
            }

    elif len(emails) > 1:
        emails_string = ','.join(emails)
        endpoint_url = '/whois/emails'
        if sort or limit or offset:
            params = {
                'emailList': emails_string,
                'sortField': sort,
                'limit': limit,
                'offset': offset
            }
        else:
            params = {
                'emailList': emails_string,
            }

    res = http_request(endpoint_url, params)
    if not res:
        return False
    return res


def get_domains_for_nameserver_command():
    # Initialize
    contents = []  # type: ignore
    context = {}
    headers = []  # type: ignore
    results = []
    markdown = ''
    # Get vars
    nameservers = argToList(demisto.args()['nameservers'])
    offset = demisto.args().get('offset', '')
    sort = demisto.args().get('sort', '')
    limit = demisto.args().get('limit', '')
    # user input validation
    if not isinstance(nameservers, list):
        return_error('Name Servers list is not formatted correctly, please try again.')
    if sort:
        if sort != 'created' and sort != 'updated':
            return_error('The parameter sort accept only these values: created/updated')
    for nameserver in nameservers:
        if re.match('^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$',
                    nameserver) is None:
            return_error('The provided name server is not valid: ' + nameserver)
    # Fetch data
    res = get_domains_for_nameserver(nameservers, offset, sort, limit)
    if res:
        # Process response - build context and markdown table
        domains = []  # type: ignore
        for nameserver in nameservers:
            domains_contents = []
            nameservers_contents = []  # type: ignore
            domains_list = []
            nameservers_context = []
            # get the entry that matches the provided nameservers each time
            nameserver_res = res[nameserver]
            domains = nameserver_res.get('domains', [])
            if not nameserver_res or not domains:
                continue

            # going over the domains associated with this nameserver, making POST request to get each categorization
            for domain in domains:
                domains_list.append(domain['domain'])
            domains_info = get_domains_categorization(domains_list)
            if domains_info:
                for domain in domains:
                    domains_contents.append({
                        'Name': domain['domain'],
                        'Security Categories': domains_info[domain['domain']]['security_categories'],
                        'Content Categories': domains_info[domain['domain']]['content_categories'],
                        'Is Current': domain['current']
                    })

            # each nameserver has its own data + associated domains attached
            nameservers_context.append({
                'TotalResults': nameserver_res['totalResults'],
                'MoreDataAvailable': nameserver_res['moreDataAvailable'],
                'ResultLimit': nameserver_res['limit'],
                'Domains': domains_contents
            })
            # each nameserver represented by 2 tables
            # Build Output
            markdown = markdown + tableToMarkdown('Domains Associated with: ' + nameserver, domains_contents, headers)
            contents.extend((nameservers_contents, domains_contents))

        context['Umbrella.AssociatedDomains(val.Nameserver && val.Nameserver == obj.Nameserver)'] = {
            'Nameserver': nameserver,
            'Data': nameservers_context
        }

    if not markdown:
        markdown = tableToMarkdown('Domains Associated with: ' + nameserver, domains_contents, headers)

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': markdown,
        'EntryContext': context
    })

    return results


def get_domains_for_nameserver(nameservers, offset, sort, limit):
    # Build & Send request
    params = {}  # type: ignore
    # single name server has different api call over multiple name servers
    if len(nameservers) == 1:
        endpoint_url = '/whois/nameservers/' + nameservers[0]
        if sort or limit or offset:
            params = {
                'sortField': sort,
                'limit': limit,
                'offset': offset
            }
    elif len(nameservers) > 1:
        nameservers_string = ','.join(nameservers)
        endpoint_url = '/whois/nameservers'
        if sort or limit or offset:
            params = {
                'nameServerList': nameservers_string,
                'sortField': sort,
                'limit': limit,
                'offset': offset
            }
        else:
            params = {
                'nameServerList': nameservers_string,
            }

    res = http_request(endpoint_url, params)
    if not res:
        return False
    return res


def get_whois_for_domain_command():
    # Initialize
    context = {}
    headers = []  # type: ignore
    results = []
    contents_nameserver = {}  # type: ignore
    contents_email = {}  # type: ignore
    table_whois = {}
    whois = {}
    execution_metrics = ExecutionMetrics()

    original_domain = demisto.args()['domain']
    domain = extract_domain_name(original_domain)

    try:
        res = get_whois_for_domain(domain)
        if res:
            # Process response - build context and markdown table
            nameservers = res.get('nameServers')
            emails = res.get('emails')
            whois = {
                'Name': res.get('domainName'),
                'RegistrarName': res.get('registrarName'),
                'LastRetrieved': res.get('timeOfLatestRealtimeCheck'),
                'Created': res.get('created'),
                'Updated': res.get('updated'),
                'Expires': res.get('expires'),
                'IANAID': res.get('registrarIANAID'),
                'LastObserved': res.get('auditUpdatedDate')
            }

            table_whois = {
                'Name': whois.get('Name'),
                'Registrar Name': whois.get('RegistrarName'),
                'Last Retrieved': whois.get('LastRetrieved'),
                'Created': whois.get('Created'),
                'Updated': whois.get('Updated'),
                'Expires': whois.get('Expires'),
                'IANAID': whois.get('IANAID'),
                'Last Observed': whois.get('LastObserved')
            }

            admin = {
                'Country': res.get('administrativeContactCountry', ),
                'Email': res.get('administrativeContactEmail', ),
                'Name': res.get('administrativeContactName'),
                'Phone': res.get('administrativeContactTelephone')
            }
            registrant = {
                'Country': res.get('registrantCountry'),
                'Email': res.get('registrantEmail'),
                'Name': res.get('registrantName'),
                'Phone': res.get('registrantTelephone'),
            }
            creation_date = res.get('created')
            registrar = {'Name': res.get('registrarName')}
            domain_status = res.get('status')
            updated_date = res.get('updated')
            expiration_date = res.get('expires')

            context[outputPaths['domain']] = {
                'Name': domain,
                'Admin': admin,
                'Registrant': registrant,
                'Registrar': registrar,
                'CreationDate': creation_date,
                'DomainStatus': domain_status,
                'UpdatedDate': updated_date,
                'ExpirationDate': expiration_date,
            }

            contents_nameserver = {'Nameservers': nameservers}
            contents_email = {'Emails': emails}

            whois.update({
                'Nameservers': nameservers,
                'Emails': emails
            })
            context['Domain.Umbrella.Whois(val.Name && val.Name == obj.Name)'] = whois
            execution_metrics.success += 1
    except RequestException as r:
        if r.response.status_code == 429:
            execution_metrics.quota_error += 1
        else:
            execution_metrics.general_error += 1
        return_results(execution_metrics.metrics)
        return_error(r.response.text)

    readable_whois = tableToMarkdown('"Umbrella Investigate" WHOIS Record Data for: ' + whois['Name'],
                                     table_whois, headers, date_fields=["Last Retrieved"])  # noqa: W504
    readable_name_servers = tableToMarkdown('Nameservers: ', contents_nameserver, headers)  # noqa: W504
    readable_email = tableToMarkdown('Email Addresses: ', contents_email, headers)

    results.append(
        CommandResults(
            raw_response=[table_whois, contents_nameserver, contents_email],
            outputs=context,
            readable_output=readable_whois + readable_name_servers + readable_email
        ))
    results = append_metrics(execution_metrics, results)

    return results


def get_whois_for_domain(domain):
    # Build & Send request
    endpoint_url = '/whois/' + domain
    res = http_request(endpoint_url)

    # Assign and validate response
    if not res or res.get('errorMessage'):
        return False
    return res


def get_malicious_domains_for_ip_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    context_dbotscore = []
    context_malicious = []
    # Get vars
    ip = demisto.args()['ip']
    # Fetch data
    res = get_malicious_domains_for_ip(ip)
    if res:
        # Process response - build context and markdown table
        domains = []
        for item in res:
            domains.append(item['name'])
        domains = get_domains_categorization(domains)
        domains_context = []
        if domains:
            for domain in domains:
                domains_context.append({
                    'Name': domain,
                    'MalwareCategories': domains[domain]['security_categories'],
                    'ContentCategories': domains[domain]['content_categories']
                })
                contents.append({
                    'Name': domain,
                    'Malware Categories': domains[domain]['security_categories'],
                    'Content Categories': domains[domain]['content_categories']
                })
                context_dbotscore.append({
                    'Indicator': domain,
                    'Type': 'domain',
                    'Vendor': 'Cisco Umbrella Investigate',
                    'Score': 3,
                    'Reliability': reliability
                })
                context_malicious.append({
                    'Name': domain,
                    'Malicious': {
                        'Vendor': 'Cisco Umbrella Investigate',
                        'Description': 'For IP ' + ip
                    }
                })

        context['Umbrella.MaliciousDomains(val.IP && val.IP == obj.IP)'] = {
            'IP': ip,
            'Data': domains_context
        }
        context[outputPaths['domain']] = context_malicious  # type: ignore
        context[outputPaths['dbotscore']] = context_dbotscore  # type: ignore

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Malicious Domains for an IP: ' + ip, contents,
                                         headers),
        'EntryContext': context
    })

    return results


def get_malicious_domains_for_ip(ip):
    # Build & Send request
    endpoint_url = '/ips/' + ip + '/latest_domains'
    res = http_request(endpoint_url)
    # Assign and validate response
    if not res:
        return False
    return res


def get_domain_using_regex_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    title_contents = []  # type: ignore
    # Get vars
    regex = demisto.args()['expression']
    start = date_to_timestamp_func(demisto.args()['start'])
    stop = date_to_timestamp_func(demisto.args().get('stop'))
    is_include_category = bool(strtobool(demisto.args().get('includeCategory')))
    limit = demisto.args().get('limit')
    node_type = demisto.args().get('type')
    # Fetch data
    res = get_domain_using_regex(regex, start, is_include_category, stop, limit, node_type)
    if res:
        matches = res.get('matches', [])
        # Process response - build context and markdown table
        domain_context = []
        for match in matches:
            contents.append({
                'Name': match['name'],
                'First Seen': match['firstSeenISO'],
                'Security Categories': match['securityCategories']
            })
            domain_context.append({
                'Name': match['name'],
                'FirstSeen': match['firstSeen'],
                'SecurityCategories': match['securityCategories']
            })

        title_contents = [{
            'Total Results': res['totalResults'],
            'More Data Available': res['moreDataAvailable'],
            'Limit': res['limit']
        }]

        context['Umbrella.DomainSearch(val.Expression && val.Expression == obj.Expression)'] = {
            'Expression': res.get('expression', regex),
            'TotalResults': res.get('totalResults', None),
            'Data': domain_context
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': [title_contents, contents],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Domain Pattern Search for: ' + regex, title_contents,
                                         headers) + tableToMarkdown('Matches: ', contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_using_regex(regex, start, is_include_category, stop, limit, node_type):
    # Build params dict
    params = {
        'start': start,
        'includecategory': is_include_category,
        'stop': stop,
        'limit': limit,
        'type': node_type
    }

    # Build & Send request
    endpoint_url = '/search/' + regex
    res = http_request(endpoint_url, params)
    # Assign and validate response
    results = res.get('totalResults', 0)
    if not results:
        return False
    return res


def get_domain_timeline_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    domain = extract_domain_name(demisto.args()['domain'])
    if re.match('[a-zA-Z\d-]{,63}(\.[a-zA-Z\d-]{,63})*', domain) is None:
        return_error('Domain is not valid')
    # Fetch data
    timeline = get_domain_timeline(domain)
    if timeline:
        # Process response - build context and markdown table
        timeline_context = []
        for item in timeline:
            contents.append({
                'Malware Categories': item['categories'],
                'Attacks': item['attacks'],
                'Threat Types': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })
            timeline_context.append({
                'MalwareCategories': item['categories'],
                'Attacks': item['attacks'],
                'ThreatTypes': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })

        context['Umbrella.Timeline(val.Domain && val.Domain == obj.Domain)'] = {
            'Domain': domain,
            'Data': timeline_context
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" Domain Timeline: ' + domain, contents, headers),
        'EntryContext': context
    })

    return results


def get_domain_timeline(domain):
    # Build & Send request
    endpoint_url = '/timeline/' + domain
    timeline = http_request(endpoint_url)

    # Assign and validate response
    if not timeline:
        return False
    return timeline


def get_ip_timeline_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    ip = demisto.args()['ip']
    is_valid = is_ip_valid(ip)
    if not is_valid:
        return_error('IP is not valid')
    # Fetch data
    timeline = get_ip_timeline(ip)
    if timeline:
        # Process response - build context and markdown table
        timeline_context = []
        for item in timeline:
            contents.append({
                'Malware Categories': item['categories'],
                'Attacks': item['attacks'],
                'Threat Types': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })
            timeline_context.append({
                'MalwareCategories': item['categories'],
                'Attacks': item['attacks'],
                'ThreatTypes': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })

        context['Umbrella.Timeline(val.IP && val.IP == obj.IP)'] = {
            'IP': ip,
            'Data': timeline_context
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" IP Timeline: ' + ip, contents, headers),
        'EntryContext': context
    })

    return results


def get_ip_timeline(ip):
    # Build & Send request
    endpoint_url = '/timeline/' + ip
    timeline = http_request(endpoint_url)

    # Assign and validate response
    if not timeline:
        return False
    return timeline


def get_url_timeline_command():
    # Initialize
    contents = []
    context = {}
    headers = []  # type: ignore
    results = []
    # Get vars
    url = demisto.args()['url']
    parsed_url = urlparse(url)
    if not bool(parsed_url.scheme) and not bool(parsed_url.netloc) and not bool(parsed_url.path):
        return_error('URL is not valid')
    # Fetch data
    timeline = get_url_timeline(url)
    if timeline:
        # Process response - build context and markdown table
        timeline_context = []
        for item in timeline:
            contents.append({
                'Malware Categories': item['categories'],
                'Attacks': item['attacks'],
                'Threat Types': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })
            timeline_context.append({
                'MalwareCategories': item['categories'],
                'Attacks': item['attacks'],
                'ThreatTypes': item['threatTypes'],
                'Timestamp': timestamp_to_date(item['timestamp']),
            })

        context['Umbrella.Timeline(val.URL && val.URL == obj.URL)'] = {
            'URL': url,
            'Data': timeline_context
        }

    results.append({
        'Type': entryTypes['note'],
        'ContentsFormat': formats['json'],
        'Contents': contents,
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('"Umbrella Investigate" URL Timeline: ' + url, contents, headers),
        'EntryContext': context
    })

    return results


def get_url_timeline(url):
    # percent encoding the url or else the API does not give response 200
    encoded_url = urllib.parse.quote_plus(url.encode('utf-8'))

    # Build & Send request
    endpoint_url = '/timeline/' + encoded_url
    timeline = http_request(endpoint_url)

    # Assign and validate response
    if not timeline:
        return False
    return timeline


''' COMMANDS MANAGER / SWITCH PANEL '''


def main() -> None:
    demisto.debug(f'Command being called is {demisto.command()}')
    try:
        handle_proxy()
        verify_threshold_params(SUSPICIOUS_THRESHOLD, MALICIOUS_THRESHOLD)
        if demisto.command() == 'test-module':
            # This is the call made when pressing the integration test button.
            http_request('/domains/categorization/google.com?showLabels')
            demisto.results('ok')
            sys.exit(0)
        elif demisto.command() == 'investigate-umbrella-domain-categorization' or demisto.command() == \
                'umbrella-domain-categorization':
            demisto.results(get_domain_categorization_command())
        elif demisto.command() == 'investigate-umbrella-domain-search' or demisto.command() == 'umbrella-domain-search':
            demisto.results(get_domain_search_command())
        elif demisto.command() == 'investigate-umbrella-domain-co-occurrences' or demisto.command() == \
                'umbrella-domain-co-occurrences':
            demisto.results(get_domain_co_occurrences_command())
        elif demisto.command() == 'investigate-umbrella-domain-related' or demisto.command() == 'umbrella-domain-related':
            demisto.results(get_domain_related_command())
        elif demisto.command() == 'investigate-umbrella-domain-security' or demisto.command() == 'umbrella-domain-security':
            demisto.results(get_domain_security_command())
        elif demisto.command() == 'investigate-umbrella-domain-dns-history' or demisto.command() == \
                'umbrella-domain-dns-history':
            demisto.results(get_domain_dns_history_command())
        elif demisto.command() == 'investigate-umbrella-ip-dns-history' or demisto.command() == 'umbrella-ip-dns-history':
            demisto.results(get_ip_dns_history_command())
        elif demisto.command() == 'investigate-umbrella-ip-malicious-domains' or demisto.command() == \
                'umbrella-ip-malicious-domains':
            demisto.results(get_ip_malicious_domains_command())
        # new-commands:
        elif demisto.command() == 'domain':
            return_results(get_domain_command())
        elif demisto.command() == 'umbrella-get-related-domains':
            demisto.results(get_related_domains_command())
        elif demisto.command() == 'umbrella-get-domain-classifiers':
            demisto.results(get_domain_classifiers_command())
        elif demisto.command() == 'umbrella-get-domain-queryvolume':
            demisto.results(get_domain_query_volume_command())
        elif demisto.command() == 'umbrella-get-domain-details':
            demisto.results(get_domain_details_command())
        elif demisto.command() == 'umbrella-get-domains-for-email-registrar':
            demisto.results(get_domains_for_email_registrar_command())
        elif demisto.command() == 'umbrella-get-domains-for-nameserver':
            demisto.results(get_domains_for_nameserver_command())
        elif demisto.command() == 'umbrella-get-whois-for-domain':
            return_results(get_whois_for_domain_command())
        elif demisto.command() == 'umbrella-get-malicious-domains-for-ip':
            demisto.results(get_malicious_domains_for_ip_command())
        elif demisto.command() == 'umbrella-get-domains-using-regex':
            demisto.results(get_domain_using_regex_command())
        elif demisto.command() == 'umbrella-get-domain-timeline':
            demisto.results(get_domain_timeline_command())
        elif demisto.command() == 'umbrella-get-ip-timeline':
            demisto.results(get_ip_timeline_command())
        elif demisto.command() == 'umbrella-get-url-timeline':
            demisto.results(get_url_timeline_command())

    except HTTPError as e:
        if e.args[0]:
            return_error(e.args[0])
        else:
            return_error(f"HTTP error with code {e.response.status_code}")

    except Exception as e:
        return_error(f'Failed to execute {demisto.command()} command.\nError:\n{str(e)}')


if __name__ in ['__main__', 'builtin', 'builtins']:
    main()
