"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  Alaska 2026, Dongil Vision                                                  ║
║  Processor Survivability and Analysis Platform                               ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Module  : task_monitor_sysinfo.py                                           ║
║  Version : 1.0.0                                                             ║
║  Date    : 2026-02-05                                                        ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

import platform
import socket
import subprocess
import re
import time
from typing import Dict, List, Any, Optional

try:
    import psutil
    HAS_PSUTIL = True
except ImportError:
    HAS_PSUTIL = False


class HwInfoCollector:
    """하드웨어 정보 수집기"""

    @staticmethod
    def get_system_info() -> Dict[str, Any]:
        """OS, hostname, Windows ID, uptime 정보"""
        uname = platform.uname()
        boot = psutil.boot_time() if HAS_PSUTIL else 0
        uptime = int(time.time() - boot) if boot else 0
        return {
            'os': f"{uname.system} {uname.release}",
            'os_version': uname.version,
            'hostname': socket.gethostname(),
            'windows_id': HwInfoCollector._get_windows_id(),
            'uptime_seconds': uptime,
            'uptime_str': HwInfoCollector._format_uptime(uptime)
        }

    @staticmethod
    def _format_uptime(seconds: int) -> str:
        """uptime을 읽기 쉬운 형식으로 변환"""
        d, r = divmod(seconds, 86400)
        h, r = divmod(r, 3600)
        m, s = divmod(r, 60)
        parts = []
        if d: parts.append(f"{d}d")
        if h: parts.append(f"{h}h")
        if m: parts.append(f"{m}m")
        parts.append(f"{s}s")
        return ' '.join(parts)

    @staticmethod
    def _get_windows_id() -> str:
        """Windows 정품인증 ID 조회"""
        if platform.system() != 'Windows':
            return 'N/A (Not Windows)'
        try:
            result = subprocess.run(
                ['wmic', 'path', 'SoftwareLicensingService', 'get', 'OA3xOriginalProductKey'],
                capture_output=True, text=True, timeout=5, creationflags=subprocess.CREATE_NO_WINDOW
            )
            lines = [l.strip() for l in result.stdout.strip().split('\n') if l.strip()]
            if len(lines) > 1 and lines[1]:
                key = lines[1]
                # 마스킹: XXXXX-*****-*****-*****-XXXXX
                parts = key.split('-')
                if len(parts) == 5:
                    return f"{parts[0]}-*****-*****-*****-{parts[4]}"
                return key
            return 'N/A'
        except Exception:
            return 'N/A'

    @staticmethod
    def _get_cpu_name() -> str:
        """정확한 CPU 모델명 조회"""
        if platform.system() == 'Windows':
            try:
                result = subprocess.run(
                    ['wmic', 'cpu', 'get', 'name'],
                    capture_output=True, text=True, timeout=5,
                    creationflags=subprocess.CREATE_NO_WINDOW
                )
                lines = [l.strip() for l in result.stdout.strip().split('\n') if l.strip()]
                if len(lines) > 1:
                    return lines[1]
            except Exception:
                pass
        return platform.processor() or 'Unknown'

    @staticmethod
    def get_cpu_info() -> Dict[str, Any]:
        """CPU 정보"""
        if not HAS_PSUTIL:
            return {'error': 'psutil not installed'}
        freq = psutil.cpu_freq()
        return {
            'name': HwInfoCollector._get_cpu_name(),
            'cores_physical': psutil.cpu_count(logical=False) or 0,
            'cores_logical': psutil.cpu_count(logical=True) or 0,
            'frequency_mhz': int(freq.current) if freq else 0,
            'frequency_max_mhz': int(freq.max) if freq and freq.max else 0,
            'usage_percent': psutil.cpu_percent(interval=0.1)
        }

    @staticmethod
    def get_gpu_info() -> List[Dict[str, Any]]:
        """GPU 정보 (Windows only, wmic 사용)"""
        if platform.system() != 'Windows':
            return []
        try:
            result = subprocess.run(
                ['wmic', 'path', 'win32_VideoController', 'get', 'name,adapterram,driverversion'],
                capture_output=True, text=True, timeout=5, creationflags=subprocess.CREATE_NO_WINDOW
            )
            gpus = []
            lines = [l.strip() for l in result.stdout.strip().split('\n') if l.strip()]
            for line in lines[1:]:  # skip header
                parts = line.split()
                if len(parts) >= 3:
                    # wmic 출력 파싱 (형식이 일정하지 않을 수 있음)
                    vram_bytes = 0
                    driver = 'N/A'
                    name_parts = []
                    for p in parts:
                        if p.isdigit() and int(p) > 1000000:
                            vram_bytes = int(p)
                        elif '.' in p and any(c.isdigit() for c in p):
                            driver = p
                        else:
                            name_parts.append(p)
                    gpus.append({
                        'name': ' '.join(name_parts) or 'Unknown',
                        'vram_mb': vram_bytes // (1024 * 1024) if vram_bytes else 0,
                        'driver_version': driver
                    })
            return gpus if gpus else [{'name': 'Unknown', 'vram_mb': 0, 'driver_version': 'N/A'}]
        except Exception:
            return []

    @staticmethod
    def get_memory_info() -> Dict[str, Any]:
        """메모리 정보"""
        if not HAS_PSUTIL:
            return {'error': 'psutil not installed'}
        mem = psutil.virtual_memory()
        return {
            'total_gb': round(mem.total / (1024**3), 1),
            'used_gb': round(mem.used / (1024**3), 1),
            'available_gb': round(mem.available / (1024**3), 1),
            'usage_percent': mem.percent
        }

    @staticmethod
    def get_storage_info() -> List[Dict[str, Any]]:
        """스토리지 정보"""
        if not HAS_PSUTIL:
            return [{'error': 'psutil not installed'}]
        result = []
        for part in psutil.disk_partitions():
            if 'cdrom' in part.opts or part.fstype == '':
                continue
            try:
                usage = psutil.disk_usage(part.mountpoint)
                result.append({
                    'device': part.device,
                    'mountpoint': part.mountpoint,
                    'fstype': part.fstype,
                    'total_gb': round(usage.total / (1024**3), 1),
                    'used_gb': round(usage.used / (1024**3), 1),
                    'free_gb': round(usage.free / (1024**3), 1),
                    'usage_percent': usage.percent
                })
            except (PermissionError, OSError):
                pass
        return result

    @staticmethod
    def get_network_interfaces() -> List[Dict[str, Any]]:
        """네트워크 인터페이스 정보"""
        if not HAS_PSUTIL:
            return [{'error': 'psutil not installed'}]
        interfaces = []
        addrs = psutil.net_if_addrs()
        stats = psutil.net_if_stats()
        for name, addr_list in addrs.items():
            if not stats.get(name, {}).isup if hasattr(stats.get(name, {}), 'isup') else True:
                continue
            ip, mac, netmask = None, None, None
            for addr in addr_list:
                if addr.family == socket.AF_INET:
                    ip = addr.address
                    netmask = addr.netmask
                elif hasattr(psutil, 'AF_LINK') and addr.family == psutil.AF_LINK:
                    mac = addr.address
            if ip and not ip.startswith('127.'):
                interfaces.append({
                    'name': name,
                    'ip': ip,
                    'mac': mac or 'N/A',
                    'netmask': netmask or 'N/A'
                })
        return interfaces

    @staticmethod
    def get_arp_table() -> List[Dict[str, str]]:
        """ARP 테이블 조회"""
        try:
            if platform.system() == 'Windows':
                result = subprocess.run(
                    ['arp', '-a'], capture_output=True, text=True, timeout=5,
                    creationflags=subprocess.CREATE_NO_WINDOW
                )
            else:
                result = subprocess.run(
                    ['arp', '-a'], capture_output=True, text=True, timeout=5
                )
            entries = []
            # Windows: "192.168.1.1    aa-bb-cc-dd-ee-ff    dynamic"
            # Linux: "? (192.168.1.1) at aa:bb:cc:dd:ee:ff [ether] on eth0"
            for line in result.stdout.split('\n'):
                line = line.strip()
                if not line:
                    continue
                # Windows 형식
                match = re.match(r'(\d+\.\d+\.\d+\.\d+)\s+([\w-]+)\s+(\w+)', line)
                if match:
                    ip, mac, typ = match.groups()
                    if mac != 'ff-ff-ff-ff-ff-ff':
                        entries.append({'ip': ip, 'mac': mac.replace('-', ':'), 'type': typ})
                    continue
                # Linux 형식
                match = re.search(r'\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([\w:]+)', line)
                if match:
                    ip, mac = match.groups()
                    entries.append({'ip': ip, 'mac': mac, 'type': 'dynamic'})
            return entries[:50]  # 최대 50개
        except Exception:
            return []

    @staticmethod
    def get_routing_table() -> List[Dict[str, str]]:
        """라우팅 테이블 조회"""
        try:
            if platform.system() == 'Windows':
                result = subprocess.run(
                    ['route', 'print', '-4'], capture_output=True, text=True, timeout=5,
                    creationflags=subprocess.CREATE_NO_WINDOW
                )
                entries = []
                in_routes = False
                for line in result.stdout.split('\n'):
                    line = line.strip()
                    if 'Network Destination' in line:
                        in_routes = True
                        continue
                    if in_routes and line:
                        parts = line.split()
                        if len(parts) >= 5 and re.match(r'\d+\.\d+\.\d+\.\d+', parts[0]):
                            entries.append({
                                'destination': parts[0],
                                'netmask': parts[1],
                                'gateway': parts[2],
                                'interface': parts[3],
                                'metric': parts[4]
                            })
                    if in_routes and not line:
                        break
                return entries[:30]  # 최대 30개
            else:
                result = subprocess.run(
                    ['ip', 'route'], capture_output=True, text=True, timeout=5
                )
                entries = []
                for line in result.stdout.split('\n'):
                    if line.strip():
                        parts = line.split()
                        if len(parts) >= 3:
                            entries.append({
                                'destination': parts[0],
                                'gateway': parts[2] if parts[1] == 'via' else 'direct',
                                'interface': parts[-1]
                            })
                return entries[:30]
        except Exception:
            return []

    @classmethod
    def collect_all(cls) -> Dict[str, Any]:
        """전체 하드웨어 정보 수집"""
        return {
            'timestamp': time.time(),
            'system': cls.get_system_info(),
            'cpu': cls.get_cpu_info(),
            'gpu': cls.get_gpu_info(),
            'memory': cls.get_memory_info(),
            'storage': cls.get_storage_info(),
            'network': {
                'interfaces': cls.get_network_interfaces(),
                'arp_table': cls.get_arp_table(),
                'routing_table': cls.get_routing_table()
            }
        }

    @classmethod
    def collect_summary(cls) -> Dict[str, Any]:
        """요약 정보만 수집 (빠른 응답용)"""
        return {
            'timestamp': time.time(),
            'system': cls.get_system_info(),
            'cpu': cls.get_cpu_info(),
            'memory': cls.get_memory_info(),
            'storage': cls.get_storage_info()
        }


class MemoryLeakDetector:
    """메모리 누수 감지기

    주기적으로 메모리 사용량을 기록하고 누수 여부를 분석합니다.
    """

    def __init__(self, interval: float = 10.0, max_samples: int = 360):
        """
        Args:
            interval: 샘플링 간격 (초, 기본 10초)
            max_samples: 최대 저장 샘플 수 (기본 360개 = 1시간)
        """
        self._interval = interval
        self._max_samples = max_samples
        self._samples: List[Dict[str, Any]] = []
        self._running = False
        self._thread: Optional[Any] = None

    def _collect_sample(self) -> Dict[str, Any]:
        """메모리 샘플 수집"""
        if not HAS_PSUTIL:
            return {'error': 'psutil not installed'}
        mem = psutil.virtual_memory()
        proc = psutil.Process()
        return {
            'timestamp': time.time(),
            'system_used_mb': round(mem.used / (1024**2), 1),
            'system_percent': mem.percent,
            'process_rss_mb': round(proc.memory_info().rss / (1024**2), 1),
            'process_vms_mb': round(proc.memory_info().vms / (1024**2), 1),
        }

    def record(self) -> Dict[str, Any]:
        """단일 샘플 기록 (수동 호출용)"""
        sample = self._collect_sample()
        self._samples.append(sample)
        if len(self._samples) > self._max_samples:
            self._samples.pop(0)
        return sample

    def start(self):
        """자동 기록 시작 (백그라운드 스레드)"""
        if self._running:
            return
        import threading
        self._running = True
        def _loop():
            while self._running:
                self.record()
                time.sleep(self._interval)
        self._thread = threading.Thread(target=_loop, daemon=True)
        self._thread.start()

    def stop(self):
        """자동 기록 중지"""
        self._running = False
        if self._thread:
            self._thread.join(timeout=2.0)
            self._thread = None

    def get_samples(self) -> List[Dict[str, Any]]:
        """전체 샘플 반환"""
        return self._samples.copy()

    def get_stats(self) -> Dict[str, Any]:
        """통계 정보 반환"""
        if not self._samples:
            return {'error': 'no samples'}

        proc_rss = [s['process_rss_mb'] for s in self._samples if 'process_rss_mb' in s]
        sys_used = [s['system_used_mb'] for s in self._samples if 'system_used_mb' in s]

        if not proc_rss:
            return {'error': 'no valid samples'}

        # 추세 분석 (선형 회귀 기울기)
        n = len(proc_rss)
        if n >= 2:
            x_mean = (n - 1) / 2
            y_mean = sum(proc_rss) / n
            numerator = sum((i - x_mean) * (y - y_mean) for i, y in enumerate(proc_rss))
            denominator = sum((i - x_mean) ** 2 for i in range(n))
            slope = numerator / denominator if denominator else 0
            # MB/분 단위로 변환
            slope_per_min = slope * (60 / self._interval)
        else:
            slope_per_min = 0

        return {
            'sample_count': n,
            'duration_sec': (self._samples[-1]['timestamp'] - self._samples[0]['timestamp']) if n > 1 else 0,
            'process_rss': {
                'first_mb': proc_rss[0],
                'last_mb': proc_rss[-1],
                'min_mb': min(proc_rss),
                'max_mb': max(proc_rss),
                'avg_mb': round(sum(proc_rss) / n, 1),
                'growth_mb': round(proc_rss[-1] - proc_rss[0], 1),
                'trend_mb_per_min': round(slope_per_min, 3),
            },
            'system_used': {
                'first_mb': sys_used[0] if sys_used else 0,
                'last_mb': sys_used[-1] if sys_used else 0,
                'min_mb': min(sys_used) if sys_used else 0,
                'max_mb': max(sys_used) if sys_used else 0,
            },
            'leak_detected': slope_per_min > 1.0,  # 분당 1MB 이상 증가 시 경고
        }

    def print_graph(self, width: int = 60, height: int = 15, metric: str = 'process_rss_mb'):
        """ASCII 그래프 출력

        Args:
            width: 그래프 너비
            height: 그래프 높이
            metric: 표시할 메트릭 ('process_rss_mb', 'system_used_mb', 'system_percent')
        """
        if not self._samples:
            print("No samples recorded yet.")
            return

        values = [s.get(metric, 0) for s in self._samples]
        if not values:
            print(f"No data for metric: {metric}")
            return

        # 데이터 리샘플링 (width에 맞게)
        if len(values) > width:
            step = len(values) / width
            resampled = []
            for i in range(width):
                idx = int(i * step)
                resampled.append(values[idx])
            values = resampled

        v_min, v_max = min(values), max(values)
        v_range = v_max - v_min if v_max != v_min else 1

        # 그래프 생성
        graph = [[' ' for _ in range(len(values))] for _ in range(height)]
        for x, v in enumerate(values):
            y = int((v - v_min) / v_range * (height - 1))
            y = height - 1 - y  # 상하 반전
            graph[y][x] = '█'

        # 출력
        unit = 'MB' if 'mb' in metric.lower() else '%'
        stats = self.get_stats()
        duration = stats.get('duration_sec', 0)

        print(f"\n{'='*70}")
        print(f" Memory Usage Graph ({metric})")
        print(f" Samples: {len(self._samples)} | Duration: {duration:.0f}s | Interval: {self._interval}s")
        print(f"{'='*70}")

        for i, row in enumerate(graph):
            if i == 0:
                label = f"{v_max:>8.1f}{unit}"
            elif i == height - 1:
                label = f"{v_min:>8.1f}{unit}"
            elif i == height // 2:
                label = f"{(v_max + v_min) / 2:>8.1f}{unit}"
            else:
                label = " " * 10
            print(f"{label} │{''.join(row)}│")

        print(f"{' '*10} └{'─'*len(values)}┘")
        print(f"{' '*10}  {'oldest':<{len(values)//2}}{'latest':>{len(values)//2}}")

        # 통계 출력
        if 'process_rss' in stats:
            rss = stats['process_rss']
            print(f"\n Process RSS: {rss['first_mb']} → {rss['last_mb']} MB "
                  f"(growth: {rss['growth_mb']:+.1f} MB, trend: {rss['trend_mb_per_min']:+.3f} MB/min)")
            if stats.get('leak_detected'):
                print(f" ⚠️  WARNING: Potential memory leak detected!")
        print(f"{'='*70}\n")

    def clear(self):
        """샘플 초기화"""
        self._samples.clear()
