import copy
import json
import re
import time
from abc import ABC
from datetime import datetime

import yaml

from nettacker.config import Config
from nettacker.core.messages import messages as _
from nettacker.core.utils.common import merge_logs_to_list, remove_sensitive_header_keys
from nettacker.database.db import find_temp_events, submit_logs_to_db, submit_temp_logs_to_db
from nettacker.logger import TerminalCodes, get_logger

log = get_logger()


class BaseLibrary(ABC):
    """Nettacker library base class."""

    client = None

    def brute_force(self):
        """Brute force method."""


class BaseEngine(ABC):
    """Nettacker engine base class."""

    library = None

    def apply_extra_data(self, *args, **kwargs):
        """Add extra data into step context."""

    def filter_large_content(self, content, filter_rate=150):
        if len(content) <= filter_rate:
            return content
        else:
            filter_rate -= 1
            filter_index = filter_rate
            for char in content[filter_rate:]:
                if char == " ":
                    return content[0:filter_index] + _("filtered_content")
                else:
                    filter_index += 1
            return content

    def get_dependent_results_from_database(self, target, module_name, scan_id, event_names):
        events = []
        for event_name in event_names.split(","):
            while True:
                event = find_temp_events(target, module_name, scan_id, event_name)
                if event:
                    events.append(json.loads(event)["response"]["conditions_results"])
                    break
                time.sleep(0.1)
        return events

    def find_and_replace_dependent_values(self, sub_step, dependent_on_temp_event):
        if isinstance(sub_step, dict):
            for key in copy.deepcopy(sub_step):
                if not isinstance(sub_step[key], (str, float, int, bytes)):
                    sub_step[key] = self.find_and_replace_dependent_values(
                        copy.deepcopy(sub_step[key]), dependent_on_temp_event
                    )
                else:
                    if isinstance(sub_step[key], str):
                        if "dependent_on_temp_event" in sub_step[key]:
                            globals().update(locals())
                            generate_new_step = copy.deepcopy(sub_step[key])
                            key_name = re.findall(
                                re.compile(
                                    "dependent_on_temp_event\\[\\S+\\]\\['\\S+\\]\\[\\S+\\]"
                                ),
                                generate_new_step,
                            )[0]
                            try:
                                key_value = eval(key_name)
                            except Exception:
                                key_value = "error"
                            sub_step[key] = sub_step[key].replace(key_name, key_value)
        if isinstance(sub_step, list):
            value_index = 0
            for key in copy.deepcopy(sub_step):
                if type(sub_step[value_index]) not in [str, float, int, bytes]:
                    sub_step[key] = self.find_and_replace_dependent_values(
                        copy.deepcopy(sub_step[value_index]), dependent_on_temp_event
                    )
                else:
                    if isinstance(sub_step[value_index], str):
                        if "dependent_on_temp_event" in sub_step[value_index]:
                            globals().update(locals())
                            generate_new_step = copy.deepcopy(sub_step[key])
                            key_name = re.findall(
                                re.compile("dependent_on_temp_event\\['\\S+\\]\\[\\S+\\]"),
                                generate_new_step,
                            )[0]
                            try:
                                key_value = eval(key_name)
                            except Exception:
                                key_value = "error"
                            sub_step[value_index] = sub_step[value_index].replace(
                                key_name, key_value
                            )
                value_index += 1
        return sub_step

    def process_conditions(
        self,
        event,
        module_name,
        target,
        scan_id,
        options,
        response,
        process_number,
        module_thread_number,
        total_module_thread_number,
        request_number_counter,
        total_number_of_requests,
    ):
        # Remove sensitive keys from headers before submitting to DB
        event = remove_sensitive_header_keys(event)
        if "save_to_temp_events_only" in event.get("response", ""):
            submit_temp_logs_to_db(
                {
                    "date": datetime.now(),
                    "target": target,
                    "module_name": module_name,
                    "scan_id": scan_id,
                    "event_name": event["response"]["save_to_temp_events_only"],
                    "port": event.get("ports", ""),
                    "event": event,
                    "data": response,
                }
            )
        if event["response"]["conditions_results"] and "save_to_temp_events_only" not in event.get(
            "response", ""
        ):
            # remove sensitive information before submitting to db

            options = copy.deepcopy(options)
            for key in Config.api:
                try:
                    del options[key]
                except KeyError:
                    continue

            del event["response"]["conditions"]
            del event["response"]["condition_type"]
            if "log" in event["response"]:
                del event["response"]["log"]
            event_request_keys = copy.deepcopy(event)
            del event_request_keys["response"]
            submit_logs_to_db(
                {
                    "date": datetime.now(),
                    "target": target,
                    "module_name": module_name,
                    "scan_id": scan_id,
                    "port": event.get("ports")
                    or event.get("port")
                    or (
                        event.get("url").split(":")[2].split("/")[0]
                        if isinstance(event.get("url"), str)
                        and len(event.get("url").split(":")) >= 3
                        and event.get("url").split(":")[2].split("/")[0].isdigit()
                        else ""
                    ),
                    "event": " ".join(yaml.dump(event_request_keys).split())
                    + " conditions: "
                    + " ".join(yaml.dump(event["response"]["conditions_results"]).split()),
                    "json_event": event,
                }
            )
            log_list = merge_logs_to_list(event["response"]["conditions_results"])
            if log_list:
                log.success_event_info(
                    _("send_success_event_from_module").format(
                        process_number,
                        module_name,
                        target,
                        module_thread_number,
                        total_module_thread_number,
                        request_number_counter,
                        total_number_of_requests,
                        " ",
                        self.filter_large_content(
                            "\n".join(
                                [
                                    TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value
                                    for key in log_list
                                ]
                            ),
                            filter_rate=100000,
                        ),
                    )
                )
            else:
                log.success_event_info(
                    _("send_success_event_from_module").format(
                        process_number,
                        module_name,
                        target,
                        module_thread_number,
                        total_module_thread_number,
                        request_number_counter,
                        total_number_of_requests,
                        " ".join(
                            [
                                TerminalCodes.YELLOW.value + key + TerminalCodes.RESET.value
                                if ":" in key
                                else TerminalCodes.GREEN.value + key + TerminalCodes.RESET.value
                                for key in yaml.dump(event_request_keys).split()
                            ]
                        ),
                        self.filter_large_content(
                            "conditions: "
                            + " ".join(
                                [
                                    TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value
                                    if ":" in key
                                    else TerminalCodes.GREEN.value
                                    + key
                                    + TerminalCodes.RESET.value
                                    for key in yaml.dump(
                                        event["response"]["conditions_results"]
                                    ).split()
                                ]
                            ),
                            filter_rate=150,
                        ),
                    )
                )
            log.verbose_info(json.dumps(event))
            return True
        else:
            del event["response"]["conditions"]
            log.verbose_info(
                _("send_unsuccess_event_from_module").format(
                    process_number,
                    module_name,
                    target,
                    module_thread_number,
                    total_module_thread_number,
                    request_number_counter,
                    total_number_of_requests,
                )
            )
            log.verbose_info(json.dumps(event))
            return "save_to_temp_events_only" in event["response"]

    def replace_dependent_values(self, sub_step, dependent_on_temp_event):
        return self.find_and_replace_dependent_values(sub_step, dependent_on_temp_event)

    def run(
        self,
        sub_step,
        module_name,
        target,
        scan_id,
        options,
        process_number,
        module_thread_number,
        total_module_thread_number,
        request_number_counter,
        total_number_of_requests,
    ):
        """Engine entry point."""
        backup_method = copy.deepcopy(sub_step["method"])
        backup_response = copy.deepcopy(sub_step["response"])
        del sub_step["method"]
        del sub_step["response"]

        for attr_name in ("ports", "usernames", "passwords"):
            if attr_name in sub_step:
                value = sub_step.pop(attr_name)
                sub_step[attr_name.rstrip("s")] = int(value) if attr_name == "ports" else value

        if "dependent_on_temp_event" in backup_response:
            temp_event = self.get_dependent_results_from_database(
                target, module_name, scan_id, backup_response["dependent_on_temp_event"]
            )
            sub_step = self.replace_dependent_values(sub_step, temp_event)

        action = getattr(self.library(), backup_method)
        for _i in range(options["retries"]):
            try:
                response = action(**sub_step)
                break
            except Exception:
                response = []

        sub_step["method"] = backup_method
        sub_step["response"] = backup_response
        sub_step["response"]["conditions_results"] = response

        self.apply_extra_data(sub_step, response)

        return self.process_conditions(
            sub_step,
            module_name,
            target,
            scan_id,
            options,
            response,
            process_number,
            module_thread_number,
            total_module_thread_number,
            request_number_counter,
            total_number_of_requests,
        )
