#!/usr/bin/env python3

import os
import subprocess
import xml.etree.ElementTree as ET
import re
import time
import socket
import threading
import sys
from datetime import datetime

from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich.live import Live
from rich.prompt import Prompt, Confirm
from rich.text import Text
from rich.align import Align
from rich.columns import Columns
from rich.box import ROUNDED

console = Console()

def is_admin():
    try:
        return os.getuid() == 0
    except AttributeError:
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin() != 0

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    except:
        return "127.0.0.1"
    finally:
        s.close()

def render_agency_header():
    logo = """
    [bold cyan]
    ██╗      █████╗ ███╗   ██╗██████╗ ███████╗██╗  ██╗
    ██║     ██╔══██╗████╗  ██║██╔══██╗██╔════╝╚██╗██╔╝
    ██║     ███████║██╔██╗ ██║██║  ██║█████╗   ╚███╔╝ 
    ██║     ██╔══██║██║╚██╗██║██║  ██║██╔══╝   ██╔██╗ 
    ███████╗██║  ██║██║ ╚████║██████╔╝███████╗██╔╝ ██╗
    ╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚═╝  ╚═╝[/]

    [dim cyan]----------------- [bold white]Advance LAN network scanner by VOID19821[/] -----------------[/]
    """
    console.print(Align.center(logo))

class IntelligenceEngine:
    def __init__(self, target):
        self.target = target
        self.nodes = []
        self.logs = []
        self.is_running = True
        self.admin = is_admin()

    def run_deep_scan(self):
        # Nmap command construction
        cmd = [
            "nmap", "-sS", "-sV", "-O", "-PR", "--max-retries", "3",
            "--top-ports", "100", "--osscan-guess", "--privileged",
            "-n", "-T3", "-oX", "-", str(self.target)
        ]
        
        if not self.admin and sys.platform != "win32":
            cmd.insert(0, "sudo")

        try:
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            xml_data = ""
            for line in iter(proc.stdout.readline, ""):
                xml_data += line
                if "</host>" in line:
                    host_block = re.search(r"<host.*?</host>", xml_data, re.DOTALL)
                    if host_block:
                        self.process_node(host_block.group(0))
                        xml_data = ""
            proc.wait()
        except Exception as e:
            self.logs.append(f"[bold red]![/] SYSTEM ERROR: {str(e)}")
        finally:
            self.is_running = False

    def process_node(self, xml_str):
        try:
            root = ET.fromstring(xml_str)
            addr_node = root.find("./address[@addrtype='ipv4']")
            if addr_node is None:
                return
            ip = addr_node.get("addr")
            
            # Detect OS
            os_match = root.find(".//osmatch")
            os_name = os_match.get("name") if os_match is not None else "Undetermined OS"
            
            # Detect Ports & Versions
            svcs = []
            for p in root.findall(".//port"):
                state_node = p.find("state")
                if state_node is not None and state_node.get("state") == "open":
                    p_num = p.get("portid")
                    s_node = p.find("service")
                    s_name = s_node.get("name") if s_node is not None else "unknown"
                    s_ver = s_node.get("version") if s_node is not None and s_node.get("version") else ""
                    svcs.append(f"{p_num}({s_name} {s_ver})")

            self.nodes.append({
                "ip": ip,
                "os": os_name[:35],
                "services": svcs,
                "severity": "HIGH" if len(svcs) > 3 else "LOW"
            })
            self.logs.append(f"[bold green]✓[/] Captured Node: {ip}")
        except Exception:
            pass

def run_session(subnet):
    engine = IntelligenceEngine(subnet)
    thread = threading.Thread(target=engine.run_deep_scan, daemon=True)
    thread.start()

    layout = Layout()
    layout.split_column(
        Layout(name="header", size=4),
        Layout(name="body", ratio=1),
        Layout(name="footer", size=3)
    )
    layout["body"].split_row(Layout(name="main", ratio=2), Layout(name="side", ratio=1))

    with Live(layout, refresh_per_second=10, screen=True):
        while engine.is_running or engine.nodes:
            results_table = Table(expand=True, border_style="cyan", box=ROUNDED)
            results_table.add_column("NODE IP", style="bold green")
            results_table.add_column("OPERATING SYSTEM", style="white")
            results_table.add_column("THREAT LEVEL", justify="center")

            # Create a snapshot of nodes to avoid RuntimeError during iteration
            current_nodes = list(engine.nodes)
            for n in current_nodes:
                lvl = f"[bold red]{n['severity']}[/]" if n['severity'] == "HIGH" else "[bold green]LOW[/]"
                results_table.add_row(n['ip'], n['os'], lvl)

            layout["header"].update(Panel(Align.center(f"[bold cyan]LANdex by Void[/] | [white]TARGET: {subnet}[/]"), border_style="blue"))
            layout["main"].update(Panel(results_table, title="Active Discovery"))
            layout["side"].update(Panel("\n".join(engine.logs[-15:]), title="Signal Logs", border_style="dim"))
            
            spinner = "▖▗▝▘"
            s = spinner[int(time.time() * 5) % 4]
            status_text = f"[bold white]{s} Scanning... {s}[/]" if engine.is_running else "[bold green]Scan Complete[/]"
            layout["footer"].update(Panel(Align.center(status_text)))
            
            if not engine.is_running:
                time.sleep(1) # Brief pause to show final result
                break
            time.sleep(0.1)

    # --- FINAL SUMMARY ---
    console.clear()
    render_agency_header()
    
    summary = Panel(
        f"[bold white]Total Nodes Found:[/] [cyan]{len(engine.nodes)}[/]\n"
        f"[bold white]High Threat Nodes:[/] [red]{len([n for n in engine.nodes if n['severity'] == 'HIGH'])}[/]\n"
        f"[bold white]Network Health:[/] [green]Vulnerable[/]",
        title="[bold green]Network Intelligence Summary[/]", border_style="green", padding=(1, 2)
    )
    
    cards = []
    for n in engine.nodes:
        svc_str = "\n".join([f"  • {s}" for s in n['services']]) or "  [dim]No open services detected[/]"
        cards.append(Panel(
            f"[bold cyan]OS:[/] {n['os']}\n[bold yellow]Open Services:[/]\n{svc_str}",
            title=f"[bold white]Host: {n['ip']}[/]",
            border_style="red" if n['severity'] == "HIGH" else "blue",
            width=50
        ))

    console.print(summary)
    console.print("\n[bold white]RESULTS:[/]")
    console.print(Columns(cards))
    console.print(f"\n[dim cyan]Scan completed at {datetime.now().strftime('%H:%M:%S')}.[/]")

if __name__ == "__main__":
    console.clear()
    render_agency_header()
    
    if not is_admin():
        # Fixed the tag [bold red]CRITICAL:[/ ] (removed trailing space)
        console.print(Panel("[bold red]CRITICAL:[/] Administrative rights not detected. Accuracy will drop.", border_style="red"))
        if not Confirm.ask("Proceed with restricted access?"):
            sys.exit()

    try:
        default_net = f"{get_local_ip()}/24"
        subnet = Prompt.ask("[bold cyan]Enter Subnet; or leave blank for auto[/]", default=default_net)
        run_session(subnet)
    except KeyboardInterrupt:
        console.print("\n[bold red]Terminated by user.[/]")