import sys
import time

from nxc.logger import NXCAdapter
from bloodhound.ad.domain import ADDC
from bloodhound.enumeration.computers import ComputerEnumerator
from bloodhound.enumeration.memberships import MembershipEnumerator
from bloodhound.enumeration.domains import DomainEnumerator


class BloodHound:
    def __init__(self, ad, hostname, host, port):
        self.ad = ad
        self.ldap = None
        self.pdc = None
        self.sessions = []
        self.hostname = hostname
        self.dc = hostname
        self.proto_logger(port, hostname, host)

    def proto_logger(self, port, hostname, host):
        self.logger = NXCAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})

    def connect(self):
        if len(self.ad.dcs()) == 0:
            self.logger.fail("Could not find a domain controller. Consider specifying a domain and/or DNS server.")
            sys.exit(1)

        if not self.ad.baseDN:
            self.logger.fail("Could not figure out the domain to query. Please specify this manually with -d")
            sys.exit(1)

        pdc = self.ad.dcs()[0]
        self.logger.debug("Using LDAP server: %s", pdc)
        self.logger.debug("Using base DN: %s", self.ad.baseDN)

        if len(self.ad.kdcs()) > 0:
            kdc = self.ad.kdcs()[0]
            self.logger.debug("Using kerberos KDC: %s", kdc)
            self.logger.debug("Using kerberos realm: %s", self.ad.realm())

        # Create a domain controller object
        self.pdc = ADDC(pdc, self.ad)
        # Create an object resolver
        self.ad.create_objectresolver(self.pdc)

    def run(self, collect, num_workers=10, disable_pooling=False, timestamp="", fileNamePrefix="", computerfile="", cachefile=None, exclude_dcs=False):
        start_time = time.time()
        if cachefile:
            self.ad.load_cachefile(cachefile)

        # Check early if we should enumerate computers as well
        do_computer_enum = any(
            method in collect
            for method in [
                "localadmin",
                "session",
                "loggedon",
                "experimental",
                "rdp",
                "dcom",
                "psremote",
            ]
        )

        if "group" in collect or "objectprops" in collect or "acl" in collect:
            # Fetch domains for later, computers if needed
            self.pdc.prefetch_info(
                "objectprops" in collect,
                "acl" in collect,
                cache_computers=do_computer_enum,
            )
            # Initialize enumerator
            membership_enum = MembershipEnumerator(self.ad, self.pdc, collect, disable_pooling)
            membership_enum.enumerate_memberships(timestamp=timestamp, fileNamePrefix=fileNamePrefix)
        elif "container" in collect:
            # Fetch domains for later, computers if needed
            self.pdc.prefetch_info(
                "objectprops" in collect,
                "acl" in collect,
                cache_computers=do_computer_enum,
            )
            # Initialize enumerator
            membership_enum = MembershipEnumerator(self.ad, self.pdc, collect, disable_pooling)
            membership_enum.do_container_collection(timestamp=timestamp, fileNamePrefix=fileNamePrefix)
        elif do_computer_enum:
            # We need to know which computers to query regardless
            # We also need the domains to have a mapping from NETBIOS -> FQDN for local admins
            self.pdc.prefetch_info("objectprops" in collect, "acl" in collect, cache_computers=True)
        elif "trusts" in collect:
            # Prefetch domains
            self.pdc.get_domains("acl" in collect)
        if "trusts" in collect or "acl" in collect or "objectprops" in collect:
            trusts_enum = DomainEnumerator(self.ad, self.pdc)
            trusts_enum.dump_domain(collect, timestamp=timestamp, fileNamePrefix=fileNamePrefix)
        if do_computer_enum:
            # If we don't have a GC server, don't use it for deconflictation
            have_gc = len(self.ad.gcs()) > 0
            computer_enum = ComputerEnumerator(
                self.ad,
                self.pdc,
                collect,
                do_gc_lookup=have_gc,
                computerfile=computerfile,
                exclude_dcs=exclude_dcs,
            )
            computer_enum.enumerate_computers(self.ad.computers, num_workers=num_workers, timestamp=timestamp, fileNamePrefix=fileNamePrefix)
        end_time = time.time()
        minutes, seconds = divmod(int(end_time - start_time), 60)
        self.logger.highlight(f"Done in {minutes}M {seconds}S")
