import nmap
import ipaddress
import logging
from typing import List, Dict, Any
import time

class NetworkScanner:
    def __init__(self):
        self.nm = nmap.PortScanner()
        self.logger = logging.getLogger(__name__)
    
    def validate_subnet(self, subnet: str) -> bool:
        """Validate if the provided subnet is valid."""
        try:
            ipaddress.ip_network(subnet, strict=False)
            return True
        except ValueError:
            return False
    
    def scan_subnet(self, subnet: str, scan_type: str = 'tcp') -> Dict[str, Any]:
        """
        Scan a subnet for live hosts using Nmap.
        
        Args:
            subnet: Network subnet in CIDR notation (e.g., '192.168.1.0/24')
            scan_type: Type of scan ('ping', 'tcp', 'syn')
        
        Returns:
            Dictionary containing scan results
        """
        if not self.validate_subnet(subnet):
            raise ValueError(f"Invalid subnet format: {subnet}")
        
        scan_results = {
            'subnet': subnet,
            'hosts': [],
            'scan_time': time.time(),
            'total_hosts': 0
        }
        
        try:
            # Configure scan arguments based on type
            if scan_type == 'ping':
                arguments = '-sn'  # Ping scan only
            elif scan_type == 'tcp':
                arguments = '-sT -F'  # TCP connect scan with fast mode
            elif scan_type == 'syn':
                arguments = '-sS -F'  # SYN stealth scan with fast mode
            else:
                arguments = '-sn'
            
            self.logger.info(f"Starting {scan_type} scan of {subnet}")
            scan_result = self.nm.scan(hosts=subnet, arguments=arguments)
            
            for host in self.nm.all_hosts():
                host_info = {
                    'ip': host,
                    'hostname': '',
                    'state': self.nm[host].state(),
                    'protocols': [],
                    'open_ports': [],
                    'mac_address': '',
                    'vendor': ''
                }
                
                # Get hostname
                if 'hostnames' in self.nm[host] and self.nm[host]['hostnames']:
                    host_info['hostname'] = self.nm[host]['hostnames'][0]['name']
                
                # Get MAC address and vendor info
                if 'addresses' in self.nm[host]:
                    if 'mac' in self.nm[host]['addresses']:
                        host_info['mac_address'] = self.nm[host]['addresses']['mac']
                        if 'vendor' in self.nm[host]:
                            host_info['vendor'] = list(self.nm[host]['vendor'].values())[0] if self.nm[host]['vendor'] else ''
                
                # Get port information if available
                for protocol in self.nm[host].all_protocols():
                    host_info['protocols'].append(protocol)
                    ports = self.nm[host][protocol].keys()
                    for port in ports:
                        port_state = self.nm[host][protocol][port]['state']
                        if port_state == 'open':
                            host_info['open_ports'].append({
                                'port': port,
                                'protocol': protocol,
                                'service': self.nm[host][protocol][port].get('name', 'unknown')
                            })
                
                scan_results['hosts'].append(host_info)
            
            scan_results['total_hosts'] = len(scan_results['hosts'])
            self.logger.info(f"Scan completed. Found {scan_results['total_hosts']} hosts")
            
        except Exception as e:
            self.logger.error(f"Scan failed: {str(e)}")
            raise Exception(f"Network scan failed: {str(e)}")
        
        return scan_results
    
    def get_network_info(self, subnet: str) -> Dict[str, Any]:
        """Get basic network information."""
        try:
            network = ipaddress.ip_network(subnet, strict=False)
            return {
                'network_address': str(network.network_address),
                'broadcast_address': str(network.broadcast_address),
                'netmask': str(network.netmask),
                'total_addresses': network.num_addresses,
                'usable_addresses': network.num_addresses - 2 if network.num_addresses > 2 else 0,
                'prefix_length': network.prefixlen
            }
        except ValueError as e:
            raise ValueError(f"Invalid network: {str(e)}")