"""
System Information Monitor - A Python package for gathering system information similar to htop/top
"""

import psutil
import platform
import time
from datetime import datetime, timedelta
import os
from typing import Dict, List, Tuple, Optional


class SystemMonitor:
    """
    A comprehensive system monitoring tool that provides information similar to htop/top
    """

    def __init__(self):
        self.boot_time = psutil.boot_time()
        self.previous_net_io = psutil.net_io_counters()
        self.previous_disk_io = psutil.disk_io_counters()
        self.last_update_time = time.time()

    def get_cpu_info(self) -> Dict:
        """
        Get comprehensive CPU information
        """
        try:
            cpu_percent = psutil.cpu_percent(interval=0.1)
            cpu_percent_per_core = psutil.cpu_percent(interval=0.1, percpu=True)
            cpu_freq = psutil.cpu_freq()
            cpu_count = {
                'physical': psutil.cpu_count(logical=False),
                'logical': psutil.cpu_count(logical=True)
            }
            cpu_stats = psutil.cpu_stats()
            cpu_times = psutil.cpu_times_percent(interval=0.1)

            return {
                'usage_percent': cpu_percent,
                'usage_per_core': cpu_percent_per_core,
                'frequency': {
                    'current': cpu_freq.current if cpu_freq else None,
                    'min': cpu_freq.min if cpu_freq else None,
                    'max': cpu_freq.max if cpu_freq else None
                },
                'count': cpu_count,
                'stats': {
                    'ctx_switches': cpu_stats.ctx_switches,
                    'interrupts': cpu_stats.interrupts,
                    'soft_interrupts': cpu_stats.soft_interrupts,
                    'syscalls': cpu_stats.syscalls
                },
                'times': {
                    'user': cpu_times.user,
                    'system': cpu_times.system,
                    'idle': cpu_times.idle,
                    'iowait': getattr(cpu_times, 'iowait', 0)
                }
            }
        except Exception as e:
            return {'error': f"CPU info error: {e}"}

    def get_memory_info(self) -> Dict:
        """
        Get memory and swap information
        """
        try:
            virtual_mem = psutil.virtual_memory()
            swap_mem = psutil.swap_memory()

            return {
                'virtual': {
                    'total': virtual_mem.total,
                    'available': virtual_mem.available,
                    'used': virtual_mem.used,
                    'free': virtual_mem.free,
                    'percent': virtual_mem.percent,
                    'cached': getattr(virtual_mem, 'cached', 0),
                    'buffers': getattr(virtual_mem, 'buffers', 0)
                },
                'swap': {
                    'total': swap_mem.total,
                    'used': swap_mem.used,
                    'free': swap_mem.free,
                    'percent': swap_mem.percent
                }
            }
        except Exception as e:
            return {'error': f"Memory info error: {e}"}

    def get_disk_info(self) -> Dict:
        """
        Get disk usage and I/O information
        """
        try:
            disk_usage = {}
            disk_partitions = psutil.disk_partitions()

            for partition in disk_partitions:
                try:
                    usage = psutil.disk_usage(partition.mountpoint)
                    disk_usage[partition.mountpoint] = {
                        'device': partition.device,
                        'fstype': partition.fstype,
                        'total': usage.total,
                        'used': usage.used,
                        'free': usage.free,
                        'percent': usage.percent
                    }
                except PermissionError:
                    continue

            # Disk I/O
            current_disk_io = psutil.disk_io_counters()
            time_diff = time.time() - self.last_update_time

            disk_io = {}
            if current_disk_io and self.previous_disk_io:
                read_bytes_ps = (current_disk_io.read_bytes - self.previous_disk_io.read_bytes) / time_diff
                write_bytes_ps = (current_disk_io.write_bytes - self.previous_disk_io.write_bytes) / time_diff

                disk_io = {
                    'read_bytes_per_sec': read_bytes_ps,
                    'write_bytes_per_sec': write_bytes_ps,
                    'total_read': current_disk_io.read_bytes,
                    'total_write': current_disk_io.write_bytes
                }

            self.previous_disk_io = current_disk_io

            return {
                'partitions': disk_usage,
                'io': disk_io
            }
        except Exception as e:
            return {'error': f"Disk info error: {e}"}

    def get_network_info(self) -> Dict:
        """
        Get network interface information and usage
        """
        try:
            net_io = psutil.net_io_counters()
            net_if_addrs = psutil.net_if_addrs()
            net_if_stats = psutil.net_if_stats()

            time_diff = time.time() - self.last_update_time

            network_io = {}
            if net_io and self.previous_net_io:
                bytes_sent_ps = (net_io.bytes_sent - self.previous_net_io.bytes_sent) / time_diff
                bytes_recv_ps = (net_io.bytes_recv - self.previous_net_io.bytes_recv) / time_diff

                network_io = {
                    'bytes_sent_per_sec': bytes_sent_ps,
                    'bytes_recv_per_sec': bytes_recv_ps,
                    'packets_sent_per_sec': (net_io.packets_sent - self.previous_net_io.packets_sent) / time_diff,
                    'packets_recv_per_sec': (net_io.packets_recv - self.previous_net_io.packets_recv) / time_diff,
                    'total_bytes_sent': net_io.bytes_sent,
                    'total_bytes_recv': net_io.bytes_recv
                }

            self.previous_net_io = net_io

            interfaces = {}
            for interface, addrs in net_if_addrs.items():
                interfaces[interface] = {
                    'addresses': [{'family': str(addr.family), 'address': addr.address} for addr in addrs],
                    'is_up': net_if_stats[interface].isup if interface in net_if_stats else False,
                    'speed': net_if_stats[interface].speed if interface in net_if_stats else 0
                }

            return {
                'io': network_io,
                'interfaces': interfaces
            }
        except Exception as e:
            return {'error': f"Network info error: {e}"}

    def get_process_info(self, limit: int = 10) -> List[Dict]:
        """
        Get information about running processes
        """
        try:
            processes = []
            for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent', 'status', 'username']):
                try:
                    process_info = proc.info
                    processes.append(process_info)
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue

            # Sort by CPU usage and limit results
            processes.sort(key=lambda x: x.get('cpu_percent', 0), reverse=True)
            return processes[:limit]
        except Exception as e:
            return [{'error': f"Process info error: {e}"}]

    def get_system_info(self) -> Dict:
        """
        Get general system information
        """
        try:
            uname = platform.uname()
            boot_time = datetime.fromtimestamp(self.boot_time)
            uptime = datetime.now() - boot_time

            return {
                'system': uname.system,
                'node_name': uname.node,
                'release': uname.release,
                'version': uname.version,
                'machine': uname.machine,
                'processor': uname.processor,
                'architecture': platform.architecture()[0],
                'python_version': platform.python_version(),
                'boot_time': boot_time.isoformat(),
                'uptime': str(uptime).split('.')[0]  # Remove microseconds
            }
        except Exception as e:
            return {'error': f"System info error: {e}"}

    def get_load_average(self) -> Dict:
        """
        Get system load average
        """
        try:
            if hasattr(os, 'getloadavg'):
                load_avg = os.getloadavg()
                return {
                    '1min': load_avg[0],
                    '5min': load_avg[1],
                    '15min': load_avg[2]
                }
            else:
                # Windows doesn't have load average, use CPU usage as alternative
                cpu_percent = psutil.cpu_percent(interval=1)
                return {
                    '1min': cpu_percent / 100,  # Approximate as fraction
                    '5min': cpu_percent / 100,
                    '15min': cpu_percent / 100
                }
        except Exception as e:
            return {'error': f"Load average error: {e}"}

    def get_all_info(self, process_limit: int = 10) -> Dict:
        """
        Get all system information in one call
        """
        self.last_update_time = time.time()

        return {
            'timestamp': datetime.now().isoformat(),
            'system': self.get_system_info(),
            'cpu': self.get_cpu_info(),
            'memory': self.get_memory_info(),
            'disk': self.get_disk_info(),
            'network': self.get_network_info(),
            'processes': self.get_process_info(process_limit),
            'load_average': self.get_load_average()
        }

    def format_bytes(self, bytes_value: int) -> str:
        """
        Format bytes to human readable format
        """
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if bytes_value < 1024.0:
                return f"{bytes_value:.2f} {unit}"
            bytes_value /= 1024.0
        return f"{bytes_value:.2f} PB"

    def format_percent(self, value: float) -> str:
        """
        Format percentage value
        """
        return f"{value:.1f}%"

    def print_dashboard(self, refresh_interval: float = 2.0):
        """
        Print a live updating dashboard similar to htop
        """
        try:
            import curses
            self._curses_dashboard(refresh_interval)
        except ImportError:
            self._simple_dashboard(refresh_interval)

    def _simple_dashboard(self, refresh_interval: float):
        """
        Simple text-based dashboard for systems without curses
        """
        import time
        import os

        try:
            while True:
                os.system('cls' if os.name == 'nt' else 'clear')
                info = self.get_all_info(process_limit=15)

                print("=" * 80)
                print(f"SYSTEM MONITOR - {info['timestamp']}")
                print("=" * 80)

                # System info
                sys_info = info['system']
                print(f"System: {sys_info.get('system', 'N/A')} {sys_info.get('release', '')}")
                print(f"Uptime: {sys_info.get('uptime', 'N/A')}")
                print(
                    f"Load: {info['load_average'].get('1min', 0):.2f} (1min) | {info['load_average'].get('5min', 0):.2f} (5min) | {info['load_average'].get('15min', 0):.2f} (15min)")
                print()

                # CPU info
                cpu_info = info['cpu']
                print(f"CPU: {cpu_info.get('usage_percent', 0):.1f}% total")
                if 'usage_per_core' in cpu_info:
                    cores = " | ".join([f"{core:.1f}%" for core in cpu_info['usage_per_core']])
                    print(f"Cores: [{cores}]")
                print()

                # Memory info
                mem_info = info['memory']['virtual']
                print(
                    f"Memory: {self.format_bytes(mem_info.get('used', 0))} / {self.format_bytes(mem_info.get('total', 0))} ({mem_info.get('percent', 0):.1f}%)")

                # Swap info
                swap_info = info['memory']['swap']
                if swap_info.get('total', 0) > 0:
                    print(
                        f"Swap:  {self.format_bytes(swap_info.get('used', 0))} / {self.format_bytes(swap_info.get('total', 0))} ({swap_info.get('percent', 0):.1f}%)")
                print()

                # Top processes
                print("TOP PROCESSES:")
                print(f"{'PID':>6} {'USER':<12} {'CPU%':>6} {'MEM%':>6} {'STATUS':<10} NAME")
                print("-" * 80)

                for proc in info['processes']:
                    if not isinstance(proc, dict) or 'error' in proc:
                        continue
                    print(f"{proc.get('pid', 0):>6} {proc.get('username', 'N/A')[:12]:<12} "
                          f"{proc.get('cpu_percent', 0):>6.1f} {proc.get('memory_percent', 0):>6.1f} "
                          f"{proc.get('status', 'N/A')[:10]:<10} {proc.get('name', 'N/A')[:30]}")

                print("\nPress Ctrl+C to exit...")
                time.sleep(refresh_interval)

        except KeyboardInterrupt:
            print("\nExiting system monitor...")

    def _curses_dashboard(self, refresh_interval: float):
        """
        Curses-based dashboard for better visualization
        """
        import curses

        def main(stdscr):
            curses.curs_set(0)  # Hide cursor
            stdscr.nodelay(1)  # Non-blocking input
            stdscr.timeout(int(refresh_interval * 1000))

            while True:
                stdscr.clear()
                height, width = stdscr.getmaxyx()

                info = self.get_all_info(process_limit=height - 15)

                # Header
                stdscr.addstr(0, 0, "=" * min(80, width - 1))
                stdscr.addstr(1, 0, f"SYSTEM MONITOR - {info['timestamp'][11:19]}")
                stdscr.addstr(2, 0, "=" * min(80, width - 1))

                # System info
                row = 3
                sys_info = info['system']
                stdscr.addstr(row, 0, f"System: {sys_info.get('system', 'N/A')} {sys_info.get('release', '')}")
                row += 1
                stdscr.addstr(row, 0, f"Uptime: {sys_info.get('uptime', 'N/A')}")
                row += 1

                # Load average and CPU
                load = info['load_average']
                cpu_info = info['cpu']
                stdscr.addstr(row, 0, f"Load: {load.get('1min', 0):.2f} | CPU: {cpu_info.get('usage_percent', 0):.1f}%")
                row += 2

                # Memory
                mem_info = info['memory']['virtual']
                mem_used = self.format_bytes(mem_info.get('used', 0))
                mem_total = self.format_bytes(mem_info.get('total', 0))
                mem_percent = mem_info.get('percent', 0)
                stdscr.addstr(row, 0, f"Memory: {mem_used} / {mem_total} ({mem_percent:.1f}%)")
                row += 1

                # Processes header
                row += 1
                stdscr.addstr(row, 0, "TOP PROCESSES:")
                row += 1
                stdscr.addstr(row, 0, f"{'PID':>6} {'USER':<12} {'CPU%':>6} {'MEM%':>6} {'STATUS':<8} NAME")
                row += 1
                stdscr.addstr(row, 0, "-" * min(80, width - 1))
                row += 1

                # Processes list
                for proc in info['processes']:
                    if row >= height - 2:
                        break
                    if not isinstance(proc, dict) or 'error' in proc:
                        continue

                    pid = str(proc.get('pid', 0))[:6]
                    user = proc.get('username', 'N/A')[:12]
                    cpu = f"{proc.get('cpu_percent', 0):.1f}"
                    mem = f"{proc.get('memory_percent', 0):.1f}"
                    status = proc.get('status', 'N/A')[:8]
                    name = proc.get('name', 'N/A')[:30]

                    line = f"{pid:>6} {user:<12} {cpu:>6} {mem:>6} {status:<8} {name}"
                    stdscr.addstr(row, 0, line[:width - 1])
                    row += 1

                # Footer
                stdscr.addstr(height - 1, 0, "Press 'q' to quit")

                stdscr.refresh()

                # Check for quit command
                key = stdscr.getch()
                if key == ord('q'):
                    break

        curses.wrapper(main)


# Convenience functions
def get_system_info() -> Dict:
    """
    Quick function to get all system information
    """
    monitor = SystemMonitor()
    return monitor.get_all_info()


def start_monitor(refresh_interval: float = 2.0):
    """
    Start the interactive system monitor
    """
    monitor = SystemMonitor()
    monitor.print_dashboard(refresh_interval)