import argparse
import json
import os
import platform
import subprocess
from dataclasses import dataclass, asdict, field
from typing import Optional, List, Dict, Any

import psutil


def _gb_from_bytes(x: float) -> float:
    return round(x / (1024**3), 2)


def _safe_int(x, default=None):
    try:
        return int(x)
    except Exception:
        return default


def _run_ps_json(ps_command: str, timeout: int = 5) -> Optional[Any]:
    """
    PowerShell komutunu çalıştırır ve ConvertTo-Json çıktısını parse eder.
    """
    try:
        out = subprocess.check_output(
            ["powershell", "-NoProfile", "-Command", ps_command],
            text=True,
            stderr=subprocess.DEVNULL,
            timeout=timeout,
        ).strip()
        if not out:
            return None
        return json.loads(out)
    except Exception:
        return None


def _is_windows() -> bool:
    return platform.system().lower().startswith("win")


def _ddr_type_from_smbios(smbios_type: Optional[int]) -> str:
    # SMBIOSMemoryType mapping (yaygın olanlar):
    # 26 = DDR4, 34 = DDR5
    if smbios_type == 34:
        return "DDR5"
    if smbios_type == 26:
        return "DDR4"
    # 24=DDR3, 20=DDR2 vb. ama her sistem düzgün doldurmuyor
    if smbios_type == 24:
        return "DDR3"
    if smbios_type == 20:
        return "DDR2"
    return "Unknown"


@dataclass
class RamModule:
    capacity_gb: float
    speed_mhz: Optional[int]
    ddr_type: str
    manufacturer: Optional[str] = None
    part_number: Optional[str] = None


@dataclass
class GpuInfo:
    name: str
    vram_gb: Optional[float] = None


@dataclass
class Info:
    os: str
    machine: str
    cpu: str
    cpu_cores: int
    cpu_threads: int

    ram_total_gb: float
    ram_used_gb: float
    ram_percent: float
    ram_ddr: str = "Unknown"
    ram_speed_mhz: Optional[int] = None
    ram_modules: List[RamModule] = field(default_factory=list)

    disk_total_gb: float = 0.0
    disk_used_gb: float = 0.0
    disk_percent: float = 0.0

    gpus: List[GpuInfo] = field(default_factory=list)


def get_cpu_name() -> str:
    # Windows'ta platform.processor bazen çirkin döner.
    if _is_windows():
        data = _run_ps_json("(Get-CimInstance Win32_Processor | Select-Object -First 1 Name) | ConvertTo-Json")
        if isinstance(data, dict) and data.get("Name"):
            return str(data["Name"]).strip()
        if isinstance(data, str) and data.strip():
            return data.strip()
    return platform.processor() or "Unknown CPU"


def get_ram_details() -> Dict[str, Any]:
    """
    Windows'ta RAM modüllerini ve ortalama hız/tip çıkarmaya çalışır.
    """
    result = {
        "ddr": "Unknown",
        "speed_mhz": None,
        "modules": [],
    }

    if not _is_windows():
        return result

    # Win32_PhysicalMemory: Capacity, Speed, SMBIOSMemoryType, Manufacturer, PartNumber
    ps = """
    $m = Get-CimInstance Win32_PhysicalMemory | Select-Object Capacity,Speed,SMBIOSMemoryType,Manufacturer,PartNumber
    $m | ConvertTo-Json
    """
    data = _run_ps_json(ps, timeout=6)
    if data is None:
        return result

    modules = data if isinstance(data, list) else [data]
    parsed_modules: List[RamModule] = []

    speeds = []
    ddr_types = []

    for m in modules:
        if not isinstance(m, dict):
            continue
        cap_bytes = _safe_int(m.get("Capacity"), 0) or 0
        speed = _safe_int(m.get("Speed"), None)
        smbios = _safe_int(m.get("SMBIOSMemoryType"), None)

        ddr = _ddr_type_from_smbios(smbios)
        cap_gb = _gb_from_bytes(cap_bytes)

        manufacturer = (m.get("Manufacturer") or None)
        part = (m.get("PartNumber") or None)
        if isinstance(part, str):
            part = part.strip() or None

        parsed_modules.append(
            RamModule(
                capacity_gb=cap_gb,
                speed_mhz=speed,
                ddr_type=ddr,
                manufacturer=str(manufacturer).strip() if isinstance(manufacturer, str) else None,
                part_number=part,
            )
        )

        if speed:
            speeds.append(speed)
        if ddr != "Unknown":
            ddr_types.append(ddr)

    # RAM tipi: en çok görünen ddr
    if ddr_types:
        result["ddr"] = max(set(ddr_types), key=ddr_types.count)

    # Hız: modüllerin medyan/ortalaması gibi; burada “en sık görülen”i seçelim
    if speeds:
        result["speed_mhz"] = max(set(speeds), key=speeds.count)

    result["modules"] = parsed_modules
    return result


def get_gpu_details() -> List[GpuInfo]:
    """
    Windows'ta Win32_VideoController ile Name + AdapterRAM (VRAM) alır.
    Bazı sürücüler AdapterRAM'i 0/None dönebilir; o zaman VRAM Unknown kalır.
    """
    gpus: List[GpuInfo] = []

    if _is_windows():
        ps = """
        $g = Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM
        $g | ConvertTo-Json
        """
        data = _run_ps_json(ps, timeout=6)
        if data is not None:
            items = data if isinstance(data, list) else [data]
            for it in items:
                if not isinstance(it, dict):
                    continue
                name = (it.get("Name") or "").strip()
                if not name:
                    continue
                # "Microsoft Basic Display Adapter" gibi şeyleri elemek istersen:
                # if "Basic Display" in name: continue

                ram_bytes = _safe_int(it.get("AdapterRAM"), None)
                vram_gb = _gb_from_bytes(ram_bytes) if ram_bytes and ram_bytes > 0 else None
                gpus.append(GpuInfo(name=name, vram_gb=vram_gb))

    # Windows dışı basit fallback
    if not gpus:
        # psutil GPU vermez; en azından boş liste döner
        pass

    # Aynı isimler tekrarlanırsa uniq yapalım
    uniq: Dict[str, GpuInfo] = {}
    for g in gpus:
        if g.name not in uniq:
            uniq[g.name] = g
    return list(uniq.values())


def collect() -> Info:
    vm = psutil.virtual_memory()
    du = psutil.disk_usage(os.path.abspath(os.sep))

    os_name = f"{platform.system()} {platform.release()}"
    machine = platform.machine()

    cpu_name = get_cpu_name()
    cpu_cores = psutil.cpu_count(logical=False) or 0
    cpu_threads = psutil.cpu_count(logical=True) or 0

    ram_details = get_ram_details()
    gpus = get_gpu_details()

    return Info(
        os=os_name,
        machine=machine,
        cpu=cpu_name,
        cpu_cores=cpu_cores,
        cpu_threads=cpu_threads,
        ram_total_gb=_gb_from_bytes(vm.total),
        ram_used_gb=_gb_from_bytes(vm.used),
        ram_percent=float(vm.percent),
        ram_ddr=ram_details.get("ddr", "Unknown"),
        ram_speed_mhz=ram_details.get("speed_mhz", None),
        ram_modules=ram_details.get("modules", []),
        disk_total_gb=_gb_from_bytes(du.total),
        disk_used_gb=_gb_from_bytes(du.used),
        disk_percent=float(du.percent),
        gpus=gpus,
    )


def pretty(info: Info, focus: Optional[str]) -> None:
    focus = (focus or "").lower().strip()

    def p(s=""):
        print(s)

    p("Your System")
    p("-" * 52)

    if focus in ("", "os"):
        p(f"OS       : {info.os}")
        p(f"Machine  : {info.machine}")

    if focus in ("", "cpu"):
        p(f"CPU      : {info.cpu}")
        p(f"Cores    : {info.cpu_cores} | Threads: {info.cpu_threads}")

    if focus in ("", "ram", "memory"):
        extra = []
        if info.ram_ddr != "Unknown":
            extra.append(info.ram_ddr)
        if info.ram_speed_mhz:
            extra.append(f"{info.ram_speed_mhz} MHz")

        p(f"RAM      : {info.ram_total_gb:.2f} GB total")
        if extra:
            p(f"          {' '.join(extra)}")
        p(f"          {info.ram_used_gb:.2f} GB used ({info.ram_percent:.0f}%)")

        # modül detayları
        if info.ram_modules:
            p("Modules  :")
            for i, m in enumerate(info.ram_modules, start=1):
                bits = [f"{m.capacity_gb:.2f} GB", m.ddr_type]
                if m.speed_mhz:
                    bits.append(f"{m.speed_mhz} MHz")
                if m.manufacturer:
                    bits.append(m.manufacturer)
                if m.part_number:
                    bits.append(m.part_number)
                p(f"          [{i}] " + " | ".join(bits))

    if focus in ("", "disk", "storage"):
        p(f"Disk     : {info.disk_total_gb:.2f} GB total")
        p(f"          {info.disk_used_gb:.2f} GB used ({info.disk_percent:.0f}%)")

    if focus in ("", "gpu", "graphics"):
        if info.gpus:
            for idx, g in enumerate(info.gpus, start=1):
                if g.vram_gb is not None:
                    p(f"GPU      : {g.name}  ({g.vram_gb:.2f} GB VRAM)")
                else:
                    p(f"GPU      : {g.name}  (VRAM: Unknown)")
        else:
            p("GPU      : Unknown / Not detected")

    p("-" * 52)


def main():
    ap = argparse.ArgumentParser(prog="hfe-sysinfo", description="System information CLI.")
    ap.add_argument("focus", nargs="?", help="ram | cpu | gpu | disk | os")
    ap.add_argument("--json", action="store_true", help="Output JSON")
    args = ap.parse_args()

    info = collect()

    if args.json:
        # dataclass içinde nested dataclass list var -> asdict çözer
        print(json.dumps(asdict(info), ensure_ascii=False, indent=2))
    else:
        pretty(info, args.focus)


if __name__ == "__main__":
    main()