"""
Company: eXonware.com
Author: Eng. Muhammad AlShehri
Email: connect@exonware.com
Version: 0.0.1.358
Generation Date: September 04, 2025

System-wide monitoring and hardware introspection utilities.
"""

import os
import platform
import time
from dataclasses import dataclass
from typing import Dict, List, Optional, Any, Tuple, Union
from pathlib import Path

import psutil
PSUTIL_AVAILABLE = True

from ..config.logging_setup import get_logger

logger = get_logger("xsystem.monitoring.system_monitor")


@dataclass
class ProcessInfo:
    """Process information structure."""
    
    pid: int
    name: str
    status: str
    cpu_percent: float
    memory_percent: float
    memory_rss: int  # Resident Set Size in bytes
    memory_vms: int  # Virtual Memory Size in bytes
    create_time: float
    num_threads: int
    username: Optional[str] = None
    cmdline: Optional[List[str]] = None
    cwd: Optional[str] = None
    exe: Optional[str] = None


@dataclass
class SystemInfo:
    """System information structure."""
    
    # Platform info
    system: str
    node: str
    release: str
    version: str
    machine: str
    processor: str
    
    # Boot time
    boot_time: float
    
    # CPU info
    cpu_count_logical: int
    cpu_count_physical: int
    cpu_freq_current: Optional[float]
    cpu_freq_min: Optional[float]
    cpu_freq_max: Optional[float]
    
    # Memory info
    memory_total: int
    memory_available: int
    memory_used: int
    memory_percent: float
    
    # Swap info
    swap_total: int
    swap_used: int
    swap_free: int
    swap_percent: float


@dataclass
class DiskInfo:
    """Disk information structure."""
    
    device: str
    mountpoint: str
    fstype: str
    total: int
    used: int
    free: int
    percent: float


@dataclass
class NetworkInfo:
    """Network interface information structure."""
    
    interface: str
    bytes_sent: int
    bytes_recv: int
    packets_sent: int
    packets_recv: int
    errin: int
    errout: int
    dropin: int
    dropout: int
    is_up: bool


@dataclass
class NetworkConnection:
    """Network connection information."""
    
    fd: int
    family: str
    type: str
    local_address: str
    local_port: int
    remote_address: str
    remote_port: int
    status: str
    pid: Optional[int] = None


class SystemMonitor:
    """
    System-wide monitoring and hardware introspection.
    
    Features:
    - Process introspection and management
    - System resource monitoring
    - Hardware information
    - Network monitoring
    - Cross-platform compatibility
    """
    
    def __init__(self):
        """Initialize system monitor."""
        if not PSUTIL_AVAILABLE:
            logger.warning("psutil not available - system monitoring will be limited")
        
        self._boot_time = None
        logger.debug("System monitor initialized")
    
    def is_available(self) -> bool:
        """Check if full system monitoring is available."""
        return PSUTIL_AVAILABLE
    
    # =============================================================================
    # PROCESS MONITORING
    # =============================================================================
    
    def list_processes(self, attrs: Optional[List[str]] = None) -> List[ProcessInfo]:
        """
        List all running processes.
        
        Args:
            attrs: Optional list of attributes to retrieve
            
        Returns:
            List of ProcessInfo objects
        """
        if not PSUTIL_AVAILABLE:
            raise RuntimeError("psutil is required for process monitoring")
        
        processes = []
        
        if attrs is None:
            attrs = ['pid', 'name', 'status', 'cpu_percent', 'memory_percent', 
                    'memory_info', 'create_time', 'num_threads', 'username']
        
        for proc in psutil.process_iter(attrs=attrs, ad_value=None):
            try:
                pinfo = proc.info
                
                memory_info = pinfo.get('memory_info')
                process_info = ProcessInfo(
                    pid=pinfo['pid'],
                    name=pinfo.get('name', 'Unknown'),
                    status=pinfo.get('status', 'Unknown'),
                    cpu_percent=pinfo.get('cpu_percent', 0.0),
                    memory_percent=pinfo.get('memory_percent', 0.0),
                    memory_rss=memory_info.rss if memory_info else 0,
                    memory_vms=memory_info.vms if memory_info else 0,
                    create_time=pinfo.get('create_time', 0.0),
                    num_threads=pinfo.get('num_threads', 0),
                    username=pinfo.get('username'),
                )
                
                processes.append(process_info)
                
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                # Process disappeared or access denied
                continue
        
        return processes
    
    def get_process(self, pid: int) -> Optional[ProcessInfo]:
        """
        Get detailed information about a specific process.
        
        Args:
            pid: Process ID
            
        Returns:
            ProcessInfo object or None if process not found
        """
        if not PSUTIL_AVAILABLE:
            raise RuntimeError("psutil is required for process monitoring")
        
        try:
            proc = psutil.Process(pid)
            
            # Get memory info
            memory_info = proc.memory_info()
            
            # Get additional details
            try:
                cmdline = proc.cmdline()
            except (psutil.AccessDenied, psutil.ZombieProcess):
                cmdline = None
            
            try:
                cwd = proc.cwd()
            except (psutil.AccessDenied, psutil.ZombieProcess):
                cwd = None
            
            try:
                exe = proc.exe()
            except (psutil.AccessDenied, psutil.ZombieProcess):
                exe = None
            
            try:
                username = proc.username()
            except (psutil.AccessDenied, psutil.ZombieProcess):
                username = None
            
            return ProcessInfo(
                pid=proc.pid,
                name=proc.name(),
                status=proc.status(),
                cpu_percent=proc.cpu_percent(),
                memory_percent=proc.memory_percent(),
                memory_rss=memory_info.rss,
                memory_vms=memory_info.vms,
                create_time=proc.create_time(),
                num_threads=proc.num_threads(),
                username=username,
                cmdline=cmdline,
                cwd=cwd,
                exe=exe,
            )
            
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            return None
    
    def find_processes_by_name(self, name: str) -> List[ProcessInfo]:
        """
        Find processes by name.
        
        Args:
            name: Process name to search for
            
        Returns:
            List of matching ProcessInfo objects
        """
        all_processes = self.list_processes()
        return [proc for proc in all_processes if name.lower() in proc.name.lower()]
    
    def kill_process(self, pid: int, timeout: float = 3.0) -> bool:
        """
        Kill a process gracefully, then forcefully if needed.
        
        Args:
            pid: Process ID to kill
            timeout: Timeout for graceful termination
            
        Returns:
            True if process was killed
        """
        if not PSUTIL_AVAILABLE:
            raise RuntimeError("psutil is required for process management")
        
        try:
            proc = psutil.Process(pid)
            
            # Try graceful termination first
            proc.terminate()
            
            try:
                proc.wait(timeout=timeout)
                logger.debug(f"Process {pid} terminated gracefully")
                return True
            except psutil.TimeoutExpired:
                # Force kill
                proc.kill()
                proc.wait()
                logger.debug(f"Process {pid} killed forcefully")
                return True
                
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            return False
    
    # =============================================================================
    # SYSTEM INFORMATION
    # =============================================================================
    
    def get_system_info(self) -> SystemInfo:
        """
        Get comprehensive system information.
        
        Returns:
            SystemInfo object with system details
        """
        # Platform information
        system_info = {
            'system': platform.system(),
            'node': platform.node(),
            'release': platform.release(),
            'version': platform.version(),
            'machine': platform.machine(),
            'processor': platform.processor(),
        }
        
        if PSUTIL_AVAILABLE:
            # Boot time
            boot_time = psutil.boot_time()
            
            # CPU information
            cpu_count_logical = psutil.cpu_count(logical=True)
            cpu_count_physical = psutil.cpu_count(logical=False)
            
            try:
                cpu_freq = psutil.cpu_freq()
                cpu_freq_current = cpu_freq.current if cpu_freq else None
                cpu_freq_min = cpu_freq.min if cpu_freq else None
                cpu_freq_max = cpu_freq.max if cpu_freq else None
            except (AttributeError, OSError):
                cpu_freq_current = cpu_freq_min = cpu_freq_max = None
            
            # Memory information
            memory = psutil.virtual_memory()
            
            # Swap information
            swap = psutil.swap_memory()
            
            system_info.update({
                'boot_time': boot_time,
                'cpu_count_logical': cpu_count_logical,
                'cpu_count_physical': cpu_count_physical or cpu_count_logical,
                'cpu_freq_current': cpu_freq_current,
                'cpu_freq_min': cpu_freq_min,
                'cpu_freq_max': cpu_freq_max,
                'memory_total': memory.total,
                'memory_available': memory.available,
                'memory_used': memory.used,
                'memory_percent': memory.percent,
                'swap_total': swap.total,
                'swap_used': swap.used,
                'swap_free': swap.free,
                'swap_percent': swap.percent,
            })
        else:
            # Limited information without psutil
            system_info.update({
                'boot_time': 0.0,
                'cpu_count_logical': os.cpu_count() or 1,
                'cpu_count_physical': os.cpu_count() or 1,
                'cpu_freq_current': None,
                'cpu_freq_min': None,
                'cpu_freq_max': None,
                'memory_total': 0,
                'memory_available': 0,
                'memory_used': 0,
                'memory_percent': 0.0,
                'swap_total': 0,
                'swap_used': 0,
                'swap_free': 0,
                'swap_percent': 0.0,
            })
        
        return SystemInfo(**system_info)
    
    def get_cpu_usage(self, interval: float = 1.0, per_cpu: bool = False) -> Union[float, List[float]]:
        """
        Get CPU usage percentage.
        
        Args:
            interval: Measurement interval in seconds
            per_cpu: Return per-CPU usage if True
            
        Returns:
            CPU usage percentage (or list if per_cpu=True)
        """
        if not PSUTIL_AVAILABLE:
            return 0.0 if not per_cpu else [0.0]
        
        return psutil.cpu_percent(interval=interval, percpu=per_cpu)
    
    def get_memory_usage(self) -> Dict[str, Any]:
        """
        Get memory usage information.
        
        Returns:
            Dictionary with memory statistics
        """
        if not PSUTIL_AVAILABLE:
            return {'total': 0, 'available': 0, 'used': 0, 'percent': 0.0}
        
        memory = psutil.virtual_memory()
        return {
            'total': memory.total,
            'available': memory.available,
            'used': memory.used,
            'percent': memory.percent,
            'free': memory.free,
            'active': getattr(memory, 'active', 0),
            'inactive': getattr(memory, 'inactive', 0),
            'buffers': getattr(memory, 'buffers', 0),
            'cached': getattr(memory, 'cached', 0),
        }
    
    def get_disk_usage(self) -> List[DiskInfo]:
        """
        Get disk usage information for all mounted disks.
        
        Returns:
            List of DiskInfo objects
        """
        if not PSUTIL_AVAILABLE:
            return []
        
        disks = []
        
        for partition in psutil.disk_partitions():
            try:
                usage = psutil.disk_usage(partition.mountpoint)
                
                disk_info = DiskInfo(
                    device=partition.device,
                    mountpoint=partition.mountpoint,
                    fstype=partition.fstype,
                    total=usage.total,
                    used=usage.used,
                    free=usage.free,
                    percent=(usage.used / usage.total) * 100 if usage.total > 0 else 0.0
                )
                
                disks.append(disk_info)
                
            except (PermissionError, OSError):
                # Skip inaccessible partitions
                continue
        
        return disks
    
    def get_network_interfaces(self) -> List[NetworkInfo]:
        """
        Get network interface statistics.
        
        Returns:
            List of NetworkInfo objects
        """
        if not PSUTIL_AVAILABLE:
            return []
        
        interfaces = []
        
        # Get interface statistics
        net_io = psutil.net_io_counters(pernic=True)
        
        # Get interface addresses to check if interface is up
        net_addrs = psutil.net_if_addrs()
        net_stats = psutil.net_if_stats()
        
        for interface, stats in net_io.items():
            # Check if interface is up
            is_up = False
            if interface in net_stats:
                is_up = net_stats[interface].isup
            
            network_info = NetworkInfo(
                interface=interface,
                bytes_sent=stats.bytes_sent,
                bytes_recv=stats.bytes_recv,
                packets_sent=stats.packets_sent,
                packets_recv=stats.packets_recv,
                errin=stats.errin,
                errout=stats.errout,
                dropin=stats.dropin,
                dropout=stats.dropout,
                is_up=is_up,
            )
            
            interfaces.append(network_info)
        
        return interfaces
    
    def get_network_connections(self, kind: str = 'inet') -> List[NetworkConnection]:
        """
        Get network connections.
        
        Args:
            kind: Connection kind ('inet', 'inet4', 'inet6', 'tcp', 'udp', 'unix', 'all')
            
        Returns:
            List of NetworkConnection objects
        """
        if not PSUTIL_AVAILABLE:
            return []
        
        connections = []
        
        try:
            for conn in psutil.net_connections(kind=kind):
                # Handle address tuples
                local_addr = conn.laddr
                remote_addr = conn.raddr
                
                local_address = local_addr.ip if local_addr else ''
                local_port = local_addr.port if local_addr else 0
                
                remote_address = remote_addr.ip if remote_addr else ''
                remote_port = remote_addr.port if remote_addr else 0
                
                connection = NetworkConnection(
                    fd=conn.fd,
                    family=conn.family.name,
                    type=conn.type.name,
                    local_address=local_address,
                    local_port=local_port,
                    remote_address=remote_address,
                    remote_port=remote_port,
                    status=conn.status,
                    pid=conn.pid,
                )
                
                connections.append(connection)
                
        except (psutil.AccessDenied, OSError):
            # May require elevated privileges
            logger.warning("Access denied when retrieving network connections")
        
        return connections
    
    # =============================================================================
    # HARDWARE INFORMATION
    # =============================================================================
    
    def get_hardware_info(self) -> Dict[str, Any]:
        """
        Get detailed hardware information.
        
        Returns:
            Dictionary with hardware details
        """
        hardware_info = {}
        
        # Basic platform info
        hardware_info.update({
            'system': platform.system(),
            'machine': platform.machine(),
            'processor': platform.processor(),
            'architecture': platform.architecture(),
        })
        
        if PSUTIL_AVAILABLE:
            # CPU information
            hardware_info['cpu'] = {
                'logical_cores': psutil.cpu_count(logical=True),
                'physical_cores': psutil.cpu_count(logical=False),
            }
            
            # Try to get CPU frequency
            try:
                cpu_freq = psutil.cpu_freq()
                if cpu_freq:
                    hardware_info['cpu'].update({
                        'current_freq_mhz': cpu_freq.current,
                        'min_freq_mhz': cpu_freq.min,
                        'max_freq_mhz': cpu_freq.max,
                    })
            except (AttributeError, OSError):
                pass
            
            # Memory information
            memory = psutil.virtual_memory()
            hardware_info['memory'] = {
                'total_bytes': memory.total,
                'total_gb': round(memory.total / (1024**3), 2),
            }
            
            # Disk information
            disks = []
            for partition in psutil.disk_partitions():
                try:
                    usage = psutil.disk_usage(partition.mountpoint)
                    disks.append({
                        'device': partition.device,
                        'mountpoint': partition.mountpoint,
                        'fstype': partition.fstype,
                        'total_bytes': usage.total,
                        'total_gb': round(usage.total / (1024**3), 2),
                    })
                except (PermissionError, OSError):
                    continue
            
            hardware_info['disks'] = disks
            
            # Network interfaces
            interfaces = []
            net_addrs = psutil.net_if_addrs()
            net_stats = psutil.net_if_stats()
            
            for interface, addrs in net_addrs.items():
                interface_info = {
                    'name': interface,
                    'addresses': [],
                }
                
                for addr in addrs:
                    interface_info['addresses'].append({
                        'family': addr.family.name,
                        'address': addr.address,
                        'netmask': addr.netmask,
                        'broadcast': addr.broadcast,
                    })
                
                if interface in net_stats:
                    stats = net_stats[interface]
                    interface_info.update({
                        'is_up': stats.isup,
                        'duplex': stats.duplex.name if stats.duplex else 'unknown',
                        'speed_mbps': stats.speed,
                        'mtu': stats.mtu,
                    })
                
                interfaces.append(interface_info)
            
            hardware_info['network_interfaces'] = interfaces
        
        return hardware_info
    
    def get_boot_time(self) -> float:
        """
        Get system boot time as timestamp.
        
        Returns:
            Boot time timestamp
        """
        if PSUTIL_AVAILABLE:
            return psutil.boot_time()
        else:
            return 0.0
    
    def get_uptime(self) -> float:
        """
        Get system uptime in seconds.
        
        Returns:
            Uptime in seconds
        """
        boot_time = self.get_boot_time()
        if boot_time > 0:
            return time.time() - boot_time
        else:
            return 0.0
    
    # =============================================================================
    # UTILITY METHODS
    # =============================================================================
    
    def get_current_user(self) -> str:
        """Get current username."""
        if PSUTIL_AVAILABLE:
            try:
                return psutil.Process().username()
            except (psutil.AccessDenied, AttributeError):
                pass
        
        return os.getenv('USER') or os.getenv('USERNAME') or 'unknown'
    
    def get_environment_variables(self) -> Dict[str, str]:
        """Get all environment variables."""
        return dict(os.environ)
    
    def get_python_info(self) -> Dict[str, Any]:
        """Get Python runtime information."""
        import sys
        
        return {
            'version': sys.version,
            'version_info': sys.version_info,
            'executable': sys.executable,
            'platform': sys.platform,
            'prefix': sys.prefix,
            'path': sys.path,
        }


# Global system monitor instance
_system_monitor = SystemMonitor()

# Convenience functions
def list_processes() -> List[ProcessInfo]:
    """List all running processes."""
    return _system_monitor.list_processes()

def get_process(pid: int) -> Optional[ProcessInfo]:
    """Get process information by PID."""
    return _system_monitor.get_process(pid)

def get_system_info() -> SystemInfo:
    """Get system information."""
    return _system_monitor.get_system_info()

def get_cpu_usage(interval: float = 1.0) -> float:
    """Get CPU usage percentage."""
    return _system_monitor.get_cpu_usage(interval)

def get_memory_usage() -> Dict[str, Any]:
    """Get memory usage information."""
    return _system_monitor.get_memory_usage()

def get_hardware_info() -> Dict[str, Any]:
    """Get hardware information."""
    return _system_monitor.get_hardware_info()

def is_monitoring_available() -> bool:
    """Check if full system monitoring is available."""
    return _system_monitor.is_available()
