import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *

""" IMPORTS """
import urllib3
import traceback
from typing import List, Dict, Optional, Tuple, Generator

# disable insecure warnings
urllib3.disable_warnings()

INTEGRATION_NAME = "Cofense Feed"
_RESULTS_PER_PAGE = 50  # Max for Cofense is 100
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"


class Client(BaseClient):
    """Implements class for miners of Cofense feed over http/https."""

    available_fields = ["all", "malware"]

    cofense_to_indicator = {
        "IPv4 Address": FeedIndicatorType.IP,
        "Domain Name": FeedIndicatorType.Domain,
        "URL": FeedIndicatorType.URL,
        "Email": FeedIndicatorType.Email,
    }

    def __init__(
            self,
            url: str,
            auth: Tuple[str, str],
            threat_type: Optional[str] = None,
            verify: bool = False,
            proxy: bool = False,
            read_time_out: Optional[float] = 120.0,
            tags: list = [],
            tlp_color: Optional[str] = None
    ):
        """Constructor of Client and BaseClient

        Arguments:
            url {str} -- url for Cofense feed
            auth {Tuple[str, str]} -- (username, password)

        Keyword Arguments:
            threat_type {Optional[str]} -- One of available_fields (default: {None})
            verify {bool} -- Should verify certificate. (default: {False})
            proxy {bool} -- Should use proxy. (default: {False})
            read_time_out {int} -- Read time out in seconds. (default: {30})
            tags {list} -- A list of tags to add to the feed.
            tlp_color {str} -- Traffic Light Protocol color.
        """
        self.read_time_out = read_time_out
        self.threat_type = (
            threat_type if threat_type in self.available_fields else "all"
        )

        # Request related attributes
        self.suffix = "/apiv1/threat/search/"
        self.tags = tags
        self.tlp_color = tlp_color

        super().__init__(url, verify=verify, proxy=proxy, auth=auth)

    def _http_request(self, *args, **kwargs) -> dict:
        if "timeout" not in kwargs:
            kwargs["timeout"] = (5.0, self.read_time_out)
        return super()._http_request(*args, **kwargs)

    def build_iterator(
            self, begin_time: Optional[int] = None, end_time: Optional[int] = None
    ) -> Generator:
        """Builds an iterator from given data filtered by start and end times.

        Keyword Arguments:
            begin_time {Optional[str, int]} --
                Where to start fetching.
                Timestamp represented in unix format. (default: {None})
            end_time {Optional[int]} --
                Time to stop fetching (if not supplied, will be time now).
                Timestamp represented in unix format. (default: {None}).
        Yields:
            Dict -- Threat from Cofense
        """
        # if not getting now
        if not end_time:
            end_time = get_now()

        payload = {
            "beginTimestamp": str(begin_time),
            "endTimestamp": str(end_time),
            "threatType": self.threat_type,
            "resultsPerPage": _RESULTS_PER_PAGE,
        }
        # For first fetch, there is only start time.
        if not begin_time:
            payload["beginTimestamp"] = str(end_time)
            del payload["endTimestamp"]

        demisto.debug(f"{INTEGRATION_NAME} - pulling {begin_time}/{end_time}")
        cur_page = 0
        total_pages = 1
        while cur_page < total_pages:
            payload["page"] = cur_page
            raw_response = self._http_request("post", self.suffix, params=payload)
            data = raw_response.get("data", {})
            if data:
                if total_pages <= 1:
                    # Call to get all pages.
                    total_pages = data.get("page", {}).get("totalPages")
                    if total_pages is None:
                        return_error('No "totalPages" in response')
                    demisto.debug(f"total_pages set to {total_pages}")

                threats = data.get("threats", [])
                for t in threats:
                    yield t
                demisto.debug(f"{INTEGRATION_NAME} - pulling {cur_page+1}/{total_pages}. page size: {_RESULTS_PER_PAGE}")
                cur_page += 1
            else:
                return_error(f'{INTEGRATION_NAME} - no "data" in response')

    @classmethod
    def _convert_block(cls, block: dict) -> Tuple[str, str]:
        """Gets a Cofense block from blockSet and enriches it.

        Args:
            block:

        Returns:
            indicator type, value
        """
        indicator_type = block.get("blockType", "")
        indicator_type = str(cls.cofense_to_indicator.get(indicator_type))
        # Only URL indicator has inside information in data_1
        if indicator_type == FeedIndicatorType.URL:
            value = block.get("data_1", {}).get("url")
        else:
            value = block.get("data_1")
            # If a domain has '*' in the value it is of type domainGlob
            if indicator_type == FeedIndicatorType.Domain and '*' in value:
                indicator_type = FeedIndicatorType.DomainGlob
        return indicator_type, value

    def process_item(self, threat: dict) -> List[dict]:
        """Gets a threat and processes them.

        Arguments:
            threat {dict} -- A threat from Cofense ("threats" key)

        Returns:
            list -- List of dicts representing indicators.

        Examples:
            >>> Client.process_item({"id": 123, "blockSet": [{"data_1": "ip", "blockType": "IPv4 Address"}]})
            [{'value': 'ip', 'type': 'IP', 'rawJSON': \
{'data_1': 'ip', 'blockType': 'IPv4 Address', 'value': 'ip', 'type': 'IP', 'threat_id': 123}}]
        """
        results = list()
        block_set: List[dict] = threat.get("blockSet", [])
        threat_id = threat.get("id")
        for block in block_set:
            indicator, value = self._convert_block(block)
            block["value"] = value
            block["type"] = indicator
            block["threat_id"] = f'[{threat_id}]({threat.get("threatDetailURL")})'
            malware_family: dict = block.get("malwareFamily", {})
            ip_detail: dict = block.get("ipDetail", {})
            if indicator:
                indicator_obj = {
                    "value": value,
                    "type": indicator,
                    "rawJSON": block,
                    "fields": {
                        "tags": self.tags,
                        "malwarefamily": malware_family.get("familyName"),
                        "description": malware_family.get("description"),
                        "sourceoriginalseverity": block.get("impact"),
                        "threattypes": {
                            "threatcategoryconfidence": block.get("confidence"),
                            "threatcategory": block.get("role")
                        },
                        "geocountry": ip_detail.get("countryIsoCode"),
                        "geolocation": f'{ip_detail.get("latitude", "")},{ip_detail.get("longitude", "")}' if ip_detail
                        else "",
                        "asn": ip_detail.get("asn"),
                        "cofensefeedthreatid": f'[{threat_id}]({threat.get("threatDetailURL")})',
                        "cofensefeedcontinentcode": ip_detail.get("continentCode"),
                        "cofensefeedlookupon": ip_detail.get("lookupOn"),
                        "cofensefeedroledescription": block.get("roleDescription"),
                        "cofensefeedasnorganization": ip_detail.get("asnOrganization"),
                        "cofensefeedcontinentname": ip_detail.get("continentName"),
                        "countryname": ip_detail.get("countryName"),
                        "cofensefeedinfrastructuretypedescription":
                            block.get("infrastructureTypeSubclass", {}).get("description"),
                        "cofensefeedisp": ip_detail.get("isp"),
                        "organization": ip_detail.get("organization"),
                        "cofensefeedpostalcode": ip_detail.get("postalCode"),
                        "cofensefeedsubdivisionisocode": ip_detail.get("subdivisionIsoCode"),
                        "cofensefeedsubdivisionname": ip_detail.get("subdivisionName"),
                        "cofensefeedtimezone": ip_detail.get("timeZone")
                    }
                }

                if self.tlp_color:
                    indicator_obj['fields']['trafficlightprotocol'] = self.tlp_color  # type: ignore

                results.append(indicator_obj)

        return results

    def process_file_item(self, threat: dict) -> List[dict]:
        """Gets a threat and processes them.

        Arguments:
            threat {dict} -- A threat from Cofense ("threats" key)

        Returns:
            list -- List of dicts representing File indicators.

        Examples:
            >>> Client.process_item({"id": 123, "executableSet": [{"md5Hex": "f57ba3e467c72bbdb44b0a65",
            "fileName": "abc.exe"}]})
            [{'value': 'f57ba3e467c72bbdb44b0a65', 'type': 'File', 'rawJSON': \
            {'md5Hex': 'f57ba3e467c72bbdb44b0a65', 'fileName': 'abc.exe', 'value': 'f57ba3e467c72bbdb44b0a65',
            'type': 'File', 'threat_id': 123}}]
        """
        results = list()
        file_set: List[dict] = threat.get("executableSet", [])
        threat_id = threat.get("id")
        for file in file_set:
            file_type = file.get("type")
            indicator_type = FeedIndicatorType.File
            value = file.get("md5Hex")
            file["value"] = value
            file["type"] = indicator_type
            file["threat_id"] = f'[{threat_id}]({threat.get("threatDetailURL")})'
            file['impact'] = file.get("severityLevel")
            malware_family: dict = file.get("malwareFamily", {})

            if indicator_type:
                indicator_obj = {
                    "value": value,
                    "type": indicator_type,
                    "rawJSON": file,
                    "fields": {
                        "tags": self.tags,
                        "malwarefamily": malware_family.get("familyName"),
                        "description": malware_family.get("description"),
                        "sourceoriginalseverity": file.get("severityLevel"),
                        "cofensefeedthreatid": f'[{threat_id}]({threat.get("threatDetailURL")})',
                        "cofensefeedfilename": file.get("fileName"),
                        "filetype": file_type,
                        "md5": file.get("md5Hex"),
                        "sha1": file.get("sha1Hex"),
                        "cofensefeedsha224": file.get("sha224Hex"),
                        "sha256": file.get("sha256Hex"),
                        "cofensefeedsha384": file.get("sha384Hex"),
                        "sha512": file.get("sha512Hex"),
                        "ssdeep": file.get("ssdeep"),
                        "fileextension": file.get("fileNameExtension"),
                        "cofensefeedentereddate": arg_to_datetime(file.get("dateEntered")).strftime(DATE_FORMAT)  # type: ignore
                        if file.get("dateEntered") else None
                    }
                }

                if self.tlp_color:
                    indicator_obj['fields']['trafficlightprotocol'] = self.tlp_color  # type: ignore

                results.append(indicator_obj)

        return results


def test_module(client: Client) -> Tuple[str, dict, dict]:
    """A simple test module

    Arguments:
        client {Client} -- Client derives from BaseClient

    Returns:
        str -- "ok" if succeeded, else raises a error.
    """
    for _ in client.build_iterator():
        return "ok", {}, {}
    return "ok", {}, {}


def fetch_indicators_command(
        client: Client,
        begin_time: Optional[int] = None,
        end_time: Optional[int] = None,
        limit: Optional[int] = None,
) -> List[Dict]:
    """Fetches the indicators from client.

    Arguments:
        client {Client} -- Client derives from BaseClient

    Keyword Arguments:
        begin_time {Optional[int]} -- Time to start fetch from (default: {None})
        end_time {Optional[int]} -- Time to stop fetch to (default: {None})
        limit {Optional[int]} -- Maximum amount of indicators to fetch. (default: {None})

    Returns: