"""
SentinelScan - Subdomain Enumeration Module
Provides functionality for discovering subdomains of a target domain.
"""

import socket
import threading
import queue
import requests
import time
from typing import List

# ============================================================
# Configuration
# ============================================================

DEFAULT_TIMEOUT = 1.0
MAX_THREADS = 50
WORDLIST = "wordlists/subdomains.txt"  # create this file later with common subdomains

# ============================================================
# Helper Functions
# ============================================================

def resolve_subdomain(subdomain: str, domain: str) -> str:
    """
    Attempt to resolve a subdomain.
    """
    fqdn = f"{subdomain}.{domain}"
    try:
        ip = socket.gethostbyname(fqdn)
        return f"[+] Found: {fqdn} -> {ip}"
    except socket.gaierror:
        return ""
    except Exception as e:
        return f"[!] Error resolving {fqdn}: {e}"


def check_http(fqdn: str) -> str:
    """
    Attempt to connect via HTTP/HTTPS to see if the subdomain is alive.
    """
    urls = [f"http://{fqdn}", f"https://{fqdn}"]
    for url in urls:
        try:
            r = requests.get(url, timeout=DEFAULT_TIMEOUT)
            if r.status_code < 400:
                return f"[+] Alive: {url} (Status {r.status_code})"
        except Exception:
            continue
    return ""


# ============================================================
# Worker Thread
# ============================================================

def worker(domain: str, q: queue.Queue, results: List[str]):
    """
    Worker thread that processes subdomains from the queue.
    """
    while not q.empty():
        sub = q.get()
        fqdn = f"{sub}.{domain}"
        resolved = resolve_subdomain(sub, domain)
        if resolved:
            print(resolved)
            results.append(resolved)
            alive = check_http(fqdn)
            if alive:
                print(alive)
                results.append(alive)
        q.task_done()


# ============================================================
# Main Function
# ============================================================

def find_subdomains(domain: str) -> List[str]:
    """
    Discover subdomains using a wordlist.
    """
    print(f"[*] Starting subdomain enumeration on {domain}...")
    results = []
    q = queue.Queue()

    # Load wordlist
    try:
        with open(WORDLIST, "r", encoding="utf-8") as f:
            words = [line.strip() for line in f if line.strip()]
    except FileNotFoundError:
        print("[!] Wordlist not found. Please create wordlists/subdomains.txt")
        return results

    # Fill queue
    for w in words:
        q.put(w)

    # Start threads
    threads = []
    for _ in range(min(MAX_THREADS, q.qsize())):
        t = threading.Thread(target=worker, args=(domain, q, results))
        t.daemon = True
        threads.append(t)
        t.start()

    # Wait for completion
    q.join()

    print(f"[*] Subdomain enumeration complete on {domain}.")
    return results


# ============================================================
# Extended Features
# ============================================================

def brute_force_subdomains(domain: str, charset="abcdefghijklmnopqrstuvwxyz", length=3) -> List[str]:
    """
    Brute force subdomains by generating combinations.
    """
    from itertools import product
    results = []
    print(f"[*] Brute forcing subdomains of length {length}...")
    for combo in product(charset, repeat=length):
        sub = "".join(combo)
        resolved = resolve_subdomain(sub, domain)
        if resolved:
            print(resolved)
            results.append(resolved)
    return results


def passive_enumeration(domain: str) -> List[str]:
    """
    Passive enumeration using online APIs (e.g., crt.sh).
    """
    results = []
    print(f"[*] Passive enumeration via crt.sh for {domain}...")
    try:
        url = f"https://crt.sh/?q=%25.{domain}&output=json"
        r = requests.get(url, timeout=DEFAULT_TIMEOUT)
        if r.status_code == 200:
            data = r.json()
            for entry in data:
                name = entry.get("name_value")
                if name and domain in name:
                    results.append(f"[+] Found via crt.sh: {name}")
    except Exception as e:
        results.append(f"[!] Passive enumeration failed: {e}")
    return results


def hybrid_scan(domain: str) -> List[str]:
    """
    Hybrid scan combining wordlist, brute force, and passive methods.
    """
    results = []
    results.extend(find_subdomains(domain))
    results.extend(brute_force_subdomains(domain, length=2))
    results.extend(passive_enumeration(domain))
    return results


# ============================================================
# Testing Stub
# ============================================================

if __name__ == "__main__":
    target_domain = "example.com"

    # Wordlist-based
    results = find_subdomains(target_domain)
    for r in results:
        print(r)

    # Brute force
    brute_results = brute_force_subdomains(target_domain, length=2)
    for r in brute_results:
        print(r)

    # Passive
    passive_results = passive_enumeration(target_domain)
    for r in passive_results:
        print(r)

    # Hybrid
    hybrid_results = hybrid_scan(target_domain)
    for r in hybrid_results:
        print(r)
