import os
import sys
import inspect
from .validators import *
from exploit.connection import Connection
from db.db import Db
from db.db import Exploit as DBExploit


class Exploit(object):
    def __init__(self, hostname, protocol, username=None, password=None):
        self.db = Db()
        self.connection = Connection()
        self.hostname = protocol + hostname
        self.username = username
        self.password = password
        self.is_logged_in = False
        self.exploit_results = {}
        self.connection.reset_session()

    def exploit(self, short_name=None):
        if self.connection.verify_socket(self.hostname) is False:
            results = {"error": "Could not connect to host."}
        elif self.username and self.password is not None and not self.login(self.hostname, self.username,
                                                                            self.password):
            results = {"error": "Unable to login with the credentials provided."}
        else:
            if short_name is not None:
                for exploit in self.db.get_exploits_by_exploit_type_short_name(short_name):
                    self.run_exploit(exploit)
            else:
                for exploit_type in self.db.get_exploit_types():
                    for exploit in self.db.get_exploits_by_exploit_type_id(exploit_type.id):
                        self.run_exploit(exploit)
            results = self.get_exploit_results()

        return results

    def run_exploit(self, exploit: DBExploit):
        if exploit.is_authenticated and not self.is_logged_in:
            pass
        else:
            self.validate_response(
                exploit, self.do_request(exploit, exploit.exploit_body if exploit.exploit_body is not None else '')
            )

    def validate_response(self, exploit: DBExploit, response):
        if self.get_validator_by_id(exploit.validator_id).validate(response):
            self.exploit_found(exploit)

    def do_request(self, exploit: DBExploit, data):
        url = self.hostname + exploit.exploit_url
        if self.connection.verify_url(url) is False:
            return None

        return self.connection.request(hostname=url, data=data,
                                       headers=eval(
                                           exploit.exploit_headers) if exploit.exploit_headers is not None else {},
                                       method=exploit.request_method,
                                       urlencode=exploit.is_url_encode)

    def exploit_found(self, exploit: DBExploit):
        self.exploit_results.update({
            exploit.id: {
                "name": exploit.name,
                "version": exploit.version,
                "exploiturl": exploit.exploit_url
            }
        })

    def login(self, hostname, username, password):
        self.is_logged_in = self.connection.login(hostname, username, password)

        return self.is_logged_in

    def get_exploit_results(self):
        exploits = self.exploit_results.copy()
        self.exploit_results.clear()

        return exploits

    @staticmethod
    def check_file(file):
        if not os.path.isfile(file) and not os.access(file, os.R_OK):
            print('[X] ' + file + ' file is missing or not readable')
            sys.exit(1)
        else:
            return file

    @staticmethod
    def get_validator_by_id(validator_id):
        attribute = '__validator_id__'
        for name, obj in inspect.getmembers(sys.modules[__name__]):
            if hasattr(obj, attribute) and getattr(obj, attribute) == validator_id:
                return obj()
        raise ValueError('Could not find Validator with validator id %d' % validator_id)
