import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import os
import re
import shutil

import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

VERSION = "v1.0.1"
USER_AGENT = "ReversingLabs XSOAR A1000 {version}".format(version=VERSION)

BASE_URL = demisto.getParam('base')
if BASE_URL[-1] == '/':
    BASE_URL = BASE_URL[0:-1]

HEADERS = {
    'Authorization': 'Token ' + demisto.getParam('token'),
    "User-Agent": USER_AGENT
}
EXTENDED = demisto.getParam('extended')
VERIFY_CERT = demisto.getParam('verify')
A1000_FIELDS = ('sha1', 'sha256', 'sha512', 'md5', 'category', 'file_type', 'file_subtype', 'identification_name',
                'identification_version', 'file_size', 'extracted_file_count', 'local_first_seen', 'local_last_seen',
                'classification_origin', 'classification_reason', 'threat_status', 'trust_factor', 'threat_level',
                'threat_name', 'summary', 'ticloud', 'aliases')

if not demisto.getParam('proxy'):
    del os.environ['HTTP_PROXY']
    del os.environ['HTTPS_PROXY']
    del os.environ['http_proxy']
    del os.environ['https_proxy']


class NoReferenceFoundError(Exception):
    def __init__(self, message="No reference was found for this input"):
        super(NoReferenceFoundError, self).__init__(message)


no_rerence_object = NoReferenceFoundError()
no_reference_message = "{original_message} - {added_message}".format(
    original_message=str(no_rerence_object),
    added_message="Reply does not contain results"
)


# pylint: disable=function-redefined
def return_error(data):  # type: ignore
    """
    Return error as result and exit - filter 404 as non-errors
    """
    if '404' in data or 'Reply does not contain results' in data:
        demisto.results(
            {
                'Type': entryTypes['note'],
                'ContentsFormat': formats['text'],
                'Contents': data
            }
        )
    else:
        demisto.results(
            {
                'Type': entryTypes['error'],
                'ContentsFormat': formats['text'],
                'Contents': data
            }
        )
    sys.exit(0)


def validate_hash(hash_value):
    """
    Validate that the given hash is valid and return the type
    """
    type_dict = {
        32: {
            'type': 'md5',
            'regex': r'([a-fA-F\d]{32})'
        },
        40: {
            'type': 'sha1',
            'regex': r'([a-fA-F\d]{40})'
        },
        64: {
            'type': 'sha256',
            'regex': r'([a-fA-F\d]{64})'
        }
    }
    if len(hash_value) not in type_dict.keys():
        return_error('Provided input string length does not match any hash type')
    if not re.match(type_dict[len(hash_value)]['regex'], hash_value):
        return_error('Provided input string is not as hash due to containing invalid characters')
    return type_dict[len(hash_value)]['type']


def validate_http(r):
    """
    Make sure that the HTTP response is valid and return relevant data if yes
    """
    if 299 >= r.status_code >= 200:
        try:
            return True, r.json()
        except Exception as e:
            return False, 'HTTP response is not JSON [{error}] - {body}'.format(error=e, body=r.text)
    elif r.status_code in (401, 403):
        return False, 'Credential error - The provided A1000 credentials/token are either incorrect or lack ' \
                      'API roles [{code}] - {body}'.format(code=r.status_code, body=r.text)
    elif r.status_code == 404:
        return False, 'No reference found - There were no results found for the provided input ' \
                      '[{code}] - {body}'.format(code=r.status_code, body=r.text)
    else:
        return False, 'An error has occurred [{code}] - {body}'.format(
            code=r.status_code,
            body=r.text
        )


def file(hash_type, hash_value):
    """
    Get the summary data from A1000 with most of the fields except TICORE
    """
    ok, r = validate_http(requests.post(
        url=BASE_URL + '/api/samples/list/',
        headers=HEADERS,
        data={
            'hash_values': hash_value,
            'fields': A1000_FIELDS
        },
        verify=VERIFY_CERT
    ))
    if not ok:
        return_error(r)

    results = r.get('results')
    if not results:
        return_error(no_reference_message)

    res = results[0]
    status = res['threat_status']
    score = {'unknown': 0, 'known': 1, 'suspicious': 2, 'malicious': 3}[status]
    prop = status.title()
    file_data = {
        prop: {
            'Vendor': 'ReversingLabs A1000',
            'Status': status
        },
        'properties_to_append': prop
    }
    md = '## ReversingLabs A1000 reputation for: {}\n'.format(hash_value)
    ec = {'DBotScore': []}  # type: ignore

    md5 = res.get('md5')
    sha1 = res.get('sha1')
    sha256 = res.get('sha256')
    sha512 = res.get('sha512')
    file_type = res.get('file_type')
    file_info = res.get('file_subtype')
    file_size = res.get('file_size')
    if md5:
        file_data['MD5'] = md5
        md += 'MD5: **' + md5 + '**\n'
        ec['DBotScore'].append({'Indicator': md5, 'Type': 'hash', 'Vendor': 'ReversingLabs A1000', 'Score': score})
    if sha1:
        file_data['SHA1'] = sha1
        md += 'SHA1: **' + sha1 + '**\n'
        ec['DBotScore'].append({'Indicator': sha1, 'Type': 'hash', 'Vendor': 'ReversingLabs A1000', 'Score': score})
    if sha256:
        file_data['SHA256'] = sha256
        md += 'SHA256: **' + sha256 + '**\n'
        ec['DBotScore'].append({'Indicator': sha256, 'Type': 'hash', 'Vendor': 'ReversingLabs A1000', 'Score': score})
    if sha512:
        file_data['SHA512'] = sha512
        md += 'SHA512: **' + sha512 + '**\n'
    if file_type:
        file_data['Type'] = file_type
    if file_info:
        file_data['Info'] = file_info
    if file_size:
        file_data['Size'] = file_size

    ec[outputPaths['file']] = file_data  # type: ignore

    md += 'ID: **{}**\n'.format(demisto.get(res, 'summary.id'))
    md += 'Malware status: **{}**\n'.format(status)
    md += 'Local first seen: **{}**\n'.format(res.get('local_first_seen'))
    md += 'Local last seen: **{}**\n'.format(res.get('local_last_seen'))
    md += 'First seen: **{}**\n'.format(demisto.gets(res, 'ticloud.first_seen'))
    md += 'Last seen: **{}**\n'.format(demisto.gets(res, 'ticloud.last_seen'))
    md += 'Trust factor: **{}**\n'.format(res.get('trust_factor'))
    if status == 'malicious':
        md += 'Threat name: **{}**\n'.format(res.get('threat_name'))
        md += 'Threat level: **{}**\n'.format(res.get('threat_level'))
    md += 'Category: **{}**\n'.format(res.get('category'))
    md += 'Classification origin: **{}**\n'.format(res.get('classification_origin'))
    md += 'Classification reason: **{}**\n'.format(res.get('classification_reason'))
    md += 'Aliases: **{}**\n'.format(','.join(res.get('aliases')))
    md += 'Extracted file count: **{}**\n'.format(res.get('extracted_file_count'))
    md += 'File type: **{}/{}**\n'.format(file_type, file_info)
    md += 'File size: **{}**\n'.format(file_size)
    md += 'Identification name: **{}**\n'.format(res.get('identification_name'))
    md += 'Identification version: **{}**\n'.format(res.get('identification_version'))
    indicators = demisto.get(res, 'summary.indicators')
    if indicators:
        md += tableToMarkdown('Indicators', indicators)

    demisto.results({'Type': entryTypes['note'], 'ContentsFormat': formats['json'],
                     'Contents': r, 'EntryContext': ec, 'HumanReadable': md})


def extracted_files():
    """
    Get the list of extracted files for a given sample
    """
    parent = demisto.getArg('hash')
    endpoint = '/api/samples/{}/extracted-files/'.format(parent)
    ok, r = validate_http(
        requests.get(
            url=BASE_URL + endpoint,
            headers=HEADERS,
            verify=VERIFY_CERT
        ))
    if not ok:
        return_error(r)

    results = r.get('results')
    if not results:
        return_error(no_reference_message)

    ec = {'DBotScore': []}  # type: ignore
    file_list = []
    file_context_list = []
    for res in results:
        sha1 = demisto.get(res, 'sample.sha1')
        status = demisto.get(res, 'sample.threat_status')
        score = {'unknown': 0, 'known': 1, 'suspicious': 2, 'malicious': 3}[status]
        prop = status.title()
        file_data = {
            'SHA1': sha1,
            'Name': res.get('filename'),
            'Info': demisto.get(res, 'sample.type_display'),
            'Size': demisto.get(res, 'sample.file_size'),
            'Path': res.get('path'),
            'Local First': demisto.get(res, 'sample.local_first_seen'),
            'Local Last': demisto.get(res, 'sample.local_last_seen'),
            'Malware Status': status,
            'Trust': demisto.get(res, 'sample.trust_factor'),
            'Threat Name': demisto.get(res, 'sample.threat_name'),
            'Threat Level': demisto.get(res, 'sample.threat_level')
        }
        file_context = {
            'SHA1': sha1,
            'Type': demisto.get(res, 'sample.file_type'),
            'Name': res.get('filename'),
            'Info': demisto.get(res, 'sample.type_display'),
            'Size': demisto.get(res, 'sample.file_size'),
            prop: {
                'Vendor': 'ReversingLabs A1000',
                'Status': status
            },
            'properties_to_append': prop
        }
        file_list.append(file_data)
        file_context_list.append(file_context)
        ec['DBotScore'].append({'Indicator': sha1, 'Type': 'hash', 'Vendor': 'ReversingLabs A1000', 'Score': score})

    md = tableToMarkdown('ReversingLabs A1000 extracted files for: {}\n'.format(parent), file_data,
                         ['SHA1', 'Name', 'Path', 'Info', 'Size', 'Local First', 'Local Last', 'Malware Status',
                          'Trust', 'Threat Name', 'Threat Level'])
    ec[outputPaths['file']] = file_context_list
    demisto.results({'Type': entryTypes['note'], 'ContentsFormat': formats['json'],
                     'Contents': r, 'EntryContext': ec, 'HumanReadable': md})


def upload():
    """
    Upload a file to A1000 for analysis
    """
    data = {}
    if demisto.getArg('comment'):
        data['comment'] = demisto.getArg('comment')
    if demisto.getArg('tags'):
        data['tags'] = demisto.getArg('tags')
    cloud = demisto.getArg('cloud_analyze')
    if cloud and cloud.lower() == 'true':
        data['analysis'] = 'cloud'
    try:
        file_entry = demisto.getFilePath(demisto.getArg('entryId'))
        data['filename'] = file_entry['name']
        with open(file_entry['path'], 'rb') as f:
            ok, r = validate_http(requests.post(
                url=BASE_URL + '/api/uploads/',
                data=data,
                files={'file': f},
                headers=HEADERS,
                verify=VERIFY_CERT
            ))
            if not ok:
                return_error(r)
            md = '## ReversingLabs A1000 file upload\n'
            md += 'Message: **{}**\n'.format(r.get('message'))
            md += 'ID: **{}**\n'.format(demisto.get(r, 'detail.id'))
            md += 'SHA1: **{}**\n'.format(demisto.get(r, 'detail.sha1'))
            md += 'Created: **{}**\n'.format(demisto.get(r, 'detail.created'))
            demisto.results(
                {'Type': entryTypes['note'], 'ContentsFormat': formats['json'], 'Contents': r, 'HumanReadable': md})
    except Exception:
        return_error('Entry ID {} is not a file'.format(demisto.getArg('entryId')))


def delete_sample():
    """
    Delete a file from A1000
    """
    ok, r = validate_http(requests.delete(
        url=BASE_URL + '/api/samples/{}/'.format(demisto.getArg('hash')),
        headers=HEADERS,
        verify=VERIFY_CERT
    ))
    if not ok:
        return_error(r)
    res = r.get('results')
    if not res:
        return_error('Deleted successfully but got wrong JSON reply')
    md = '## ReversingLabs A1000 file delete\n'
    md += 'Message: **{}**\n'.format(res.get('message'))
    md += 'MD5: **{}**\n'.format(demisto.get(res, 'detail.md5'))
    md += 'SHA1: **{}**\n'.format(demisto.get(res, 'detail.sha1'))
    md += 'SHA256: **{}**\n'.format(demisto.get(res, 'detail.sha256'))
    demisto.results({'Type': entryTypes['note'], 'ContentsFormat': formats['json'], 'Contents': r, 'HumanReadable': md})


def download():
    """
    Download a sample from A1000
    """
    hash_value = demisto.getArg('hash')
    r = requests.get(
        url=BASE_URL + '/api/samples/{}/download/'.format(hash_value),
        headers=HEADERS,
        stream=True,
        verify=VERIFY_CERT
    )
    if r.status_code < 200 or r.status_code > 299:
        return_error('Bad HTTP response [{code}] - {body}'.format(code=r.status_code, body=r.text))
    filename = hash_value + '.bin'
    with open(filename, 'wb') as f:
        r.raw.decode_content = True
        shutil.copyfileobj(r.raw, f)
    demisto.results(file_result_existing_file(filename))


def reanalyze():
    """
    Re-Analyze a sample already existing on A1000
    """
    ok, r = validate_http(requests.post(
        url=BASE_URL + '/api/samples/{}/analyze/'.format(demisto.getArg('hash')),
        headers=HEADERS,
        data={'analysis': 'cloud'},
        verify=VERIFY_CERT
    ))
    if not ok:
        return_error(r)
    md = '## ReversingLabs A1000 file re-analyze\n'
    md += 'Message: **{}**\n'.format(r.get('message'))
    md += 'MD5: **{}**\n'.format(demisto.get(r, 'detail.md5'))
    md += 'SHA1: **{}**\n'.format(demisto.get(r, 'detail.sha1'))
    md += 'SHA256: **{}**\n'.format(demisto.get(r, 'detail.sha256'))
    demisto.results({'Type': entryTypes['note'], 'ContentsFormat': formats['json'], 'Contents': r, 'HumanReadable': md})


def unpacked():
    """
    Download samples obtained through the unpacking process
    """
    hash_value = demisto.getArg('hash')
    r = requests.get(
        url=BASE_URL + '/api/samples/{}/unpacked/'.format(hash_value),
        headers=HEADERS,
        stream=True,
        verify=VERIFY_CERT
    )
    if r.status_code < 200 or r.status_code > 299:
        return_error('Bad HTTP response [{code}] - {body}'.format(code=r.status_code, body=r.text))
    filename = hash_value + '.zip'
    with open(filename, 'wb') as f:
        r.raw.decode_content = True
        shutil.copyfileobj(r.raw, f)
    demisto.results(file_result_existing_file(filename))


if __name__ in ('__main__', '__builtin__', 'builtins'):
    if demisto.command() == 'test-module':
        ok, r = validate_http(requests.get(
            url=BASE_URL + '/api/samples/21841b32c6165b27dddbd4d6eb3a672defe54271/ticloud/',
            headers=HEADERS,
            verify=VERIFY_CERT
        ))
        if ok:
            demisto.results('ok')
        else:
            return_error(r)
    elif demisto.command() == 'file':
        hash_value = demisto.args()['file']
        hash_type = validate_hash(hash_value)
        file(hash_type, hash_value)
    elif demisto.command() == 'reversinglabs-upload':
        upload()
    elif demisto.command() == 'reversinglabs-delete':
        delete_sample()
    elif demisto.command() == 'reversinglabs-extracted-files':
        extracted_files()
    elif demisto.command() == 'reversinglabs-download':
        download()
    elif demisto.command() == 'reversinglabs-analyze':
        reanalyze()
    elif demisto.command() == 'reversinglabs-download-unpacked':
        unpacked()
    else:
        return_error('Command [{}] not implemented'.format(demisto.command()))
