#!/usr/bin/env python3
# ══════════════════════════════════════════════════════════════════
# nux — Nucora Linux Paket Yöneticisi v4.1.0
# ══════════════════════════════════════════════════════════════════
#
# Paket sınıf sistemi: app / base / tool
# - app:  Son kullanıcı uygulamaları
# - base: Sistem/runtime bağımlılıkları (upgrade'de gizli)
# - tool: Nucora sistem araçları (korumalı)
#
# ══════════════════════════════════════════════════════════════════

import os
import re
import sys
import json
import tarfile
import hashlib
import shutil
import tempfile
import subprocess
import urllib.request
import urllib.error
import gzip
import time
import socket
import threading
import warnings
from pathlib import Path
from datetime import datetime
from functools import cmp_to_key
from typing import List, Dict, Set, Optional, Tuple

warnings.filterwarnings("ignore", category=DeprecationWarning)

CLIENT_VERSION = "4.1.0"

REPO_URL = "https://repo.nucoralinux.com.tr/nux"
REPO_INDEX = f"{REPO_URL}/index.json"

KURULU_DB = "/var/lib/nux/installed.json"
CACHE_DIR = "/var/cache/nux"
LOG_FILE = "/var/log/nux.log"
HISTORY_FILE = "/var/lib/nux/history.json"
PIN_FILE = "/var/lib/nux/pinned.json"
LOCK_FILE = "/var/lock/nux.lock"
INDEX_CACHE = "/var/cache/nux/index.json"
SCRIPTS_ROOT = "/var/lib/nux/scripts"
SNAPSHOT_DIR = "/var/lib/nux/snapshots"
EXPORT_FILE = "/var/lib/nux/packages.list"

QUIET = False
ASSUME_YES = False
VERBOSE = False

PKG_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9+.-]*$")

IGNORED_DEPS = {
    "apt-transport-https", "software-properties-common",
    "lsb-release", "gnupg", "gnupg2", "dirmngr",
    "debian-archive-keyring", "ubuntu-keyring", "nucora-archive-keyring",
    "brave-keyring",
}

IGNORED_DEP_SUFFIXES = ("-keyring", "-archive-keyring")

PROTECTED_PACKAGES = {
    "nux", "bash", "coreutils", "libc6", "libstdc++6",
    "sudo", "tar", "gzip", "xz-utils", "bzip2",
    "util-linux", "base-files", "tzdata", "passwd", "login",
    "dpkg", "apt", "init", "sed", "grep", "findutils",
    "mount", "hostname", "ncurses-base", "debianutils",
    "diffutils", "dash", "perl-base", "mawk", "sensible-utils",
}

TOOL_PACKAGES = {"nux"}
TOOL_PREFIXES = ("nucora-",)

LIB_SECTIONS = {"libs", "libdevel", "oldlibs", "debug"}

opener = urllib.request.build_opener()
opener.addheaders = [('User-Agent', f'nux/{CLIENT_VERSION} (Nucora Linux)')]
urllib.request.install_opener(opener)

# ══════════════════════════════════════════════════════════════════
# RENKLER VE TEMA
# ══════════════════════════════════════════════════════════════════

R = "\033[1;31m"
G = "\033[1;32m"
Y = "\033[1;33m"
C = "\033[1;36m"
W = "\033[1;37m"
M = "\033[1;35m"
B = "\033[1;34m"
RE = "\033[0m"
DIM = "\033[2m"
BOLD = "\033[1m"
ITALIC = "\033[3m"
UNDERLINE = "\033[4m"

# Box drawing
BOX_TL = "╔"
BOX_TR = "╗"
BOX_BL = "╚"
BOX_BR = "╝"
BOX_H = "═"
BOX_V = "║"
BOX_LT = "╠"
BOX_RT = "╣"
LINE_H = "─"
LINE_V = "│"
TREE_T = "├"
TREE_L = "└"
TREE_I = "│"
TREE_DASH = "──"

# Sınıf renkleri ve etiketleri
CLASS_COLORS = {"app": G, "base": Y, "tool": M}
CLASS_LABELS = {"app": "[APP]", "base": "[BASE]", "tool": "[TOOL]"}


def renkli(msg, renk):
    return f"{renk}{msg}{RE}"


def class_badge(cls):
    color = CLASS_COLORS.get(cls, W)
    label = CLASS_LABELS.get(cls, f"[{cls.upper()}]")
    return f"{color}{label}{RE}"


def box_line(width=60):
    return f"{C}{BOX_H * width}{RE}"


def box_header(title, width=60):
    inner = width - 4
    lines = []
    lines.append(f"{C}{BOX_TL}{BOX_H * (width-2)}{BOX_TR}{RE}")
    lines.append(f"{C}{BOX_V}{RE}  {W}{title:<{inner}}{RE}{C}{BOX_V}{RE}")
    lines.append(f"{C}{BOX_BL}{BOX_H * (width-2)}{BOX_BR}{RE}")
    return "\n".join(lines)


def table_row(cols, widths, colors=None):
    parts = []
    for i, (col, w) in enumerate(zip(cols, widths)):
        color = colors[i] if colors and i < len(colors) else ""
        reset = RE if color else ""
        parts.append(f"{color}{str(col):<{w}}{reset}")
    return f"  {LINE_V} " + f" {LINE_V} ".join(parts) + f" {LINE_V}"


def table_separator(widths):
    parts = [LINE_H * (w + 2) for w in widths]
    return f"  ├{'┼'.join(parts)}┤"


def table_top(widths):
    parts = [LINE_H * (w + 2) for w in widths]
    return f"  ┌{'┬'.join(parts)}┐"


def table_bottom(widths):
    parts = [LINE_H * (w + 2) for w in widths]
    return f"  └{'┴'.join(parts)}┘"


class LockError(Exception):
    pass


# ══════════════════════════════════════════════════════════════════
# SPINNER ANİMASYONU
# ══════════════════════════════════════════════════════════════════

class Spinner:
    FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]

    def __init__(self, msg="İşleniyor..."):
        self.msg = msg
        self._stop = False
        self._thread = None

    def _spin(self):
        i = 0
        while not self._stop:
            frame = self.FRAMES[i % len(self.FRAMES)]
            sys.stdout.write(f"\r  {C}{frame}{RE} {self.msg}")
            sys.stdout.flush()
            time.sleep(0.08)
            i += 1
        sys.stdout.write(f"\r  {G}✓{RE} {self.msg}  \n")
        sys.stdout.flush()

    def start(self):
        self._stop = False
        self._thread = threading.Thread(target=self._spin, daemon=True)
        self._thread.start()

    def stop(self):
        self._stop = True
        if self._thread:
            self._thread.join(timeout=1)

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, *args):
        self.stop()


# ══════════════════════════════════════════════════════════════════
# LOCALE FIX
# ══════════════════════════════════════════════════════════════════

def cmd_env():
    env = os.environ.copy()
    env["LC_ALL"] = "C"
    env["LANG"] = "C"
    env["LANGUAGE"] = "C"
    return env


# ══════════════════════════════════════════════════════════════════
# TEMEL YARDIMCILAR
# ══════════════════════════════════════════════════════════════════

def ensure_dirs():
    for d in ["/var/lib/nux", CACHE_DIR, "/var/log", "/var/lock",
              SCRIPTS_ROOT, SNAPSHOT_DIR]:
        Path(d).mkdir(parents=True, exist_ok=True)


def log(msg, prefix="[*]", renk=W, force=False):
    if not QUIET or force:
        print(renkli(f"{prefix} {msg}", renk))
    try:
        ensure_dirs()
        with open(LOG_FILE, "a") as f:
            f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {prefix} {msg}\n")
    except Exception:
        pass


def basari(msg):   log(msg, "[✓]", G)
def bilgi(msg):    log(msg, "[*]", C)
def uyari(msg):    log(msg, "[!]", Y)
def hata(msg):
    log(msg, "[✗]", R, force=True)
    return 1


def debug(msg):
    if VERBOSE:
        print(renkli(f"[DBG] {msg}", DIM))


def root_kontrol():
    if os.geteuid() != 0:
        return hata("Root yetkisi gerekli. 'sudo nux' ile çalıştırın.")
    return 0


def onayla(msg):
    if ASSUME_YES:
        return True
    try:
        cevap = input(renkli(f"[?] {msg} [E/h]: ", Y)).lower().strip()
        return cevap in ("", "e", "evet", "y", "yes")
    except (EOFError, KeyboardInterrupt):
        print()
        return False


def acquire_lock():
    if Path(LOCK_FILE).exists():
        pid = Path(LOCK_FILE).read_text().strip()
        if pid == str(os.getpid()):
            return
        if pid and Path(f"/proc/{pid}").exists():
            raise LockError(f"Başka bir nux işlemi çalışıyor (PID: {pid})")
    ensure_dirs()
    Path(LOCK_FILE).write_text(str(os.getpid()))


def release_lock():
    Path(LOCK_FILE).unlink(missing_ok=True)


# ══════════════════════════════════════════════════════════════════
# PAKET ADI VE SINIF
# ══════════════════════════════════════════════════════════════════

def gecerli_paket_adi(name: str) -> bool:
    return bool(name and PKG_NAME_RE.fullmatch(name))


def gecersiz_db_kayitlari(db: Dict) -> List[str]:
    return [name for name in db if not gecerli_paket_adi(name)]


def is_tool_package(name: str) -> bool:
    if name in TOOL_PACKAGES:
        return True
    for prefix in TOOL_PREFIXES:
        if name.startswith(prefix):
            return True
    return False


def paket_sinifi(meta: Dict) -> str:
    return meta.get("class", "app")


def paket_sinifi_renk(cls: str) -> str:
    return CLASS_COLORS.get(cls, W)


# ══════════════════════════════════════════════════════════════════
# PROGRESS BAR
# ══════════════════════════════════════════════════════════════════

class ProgressBar:
    def __init__(self, total, desc="", toplam_adim=1, mevcut_adim=1):
        self.total = total
        self.desc = desc
        self.toplam_adim = toplam_adim
        self.mevcut_adim = mevcut_adim
        self.downloaded = 0
        self._start_time = time.monotonic()
        self._last_time = self._start_time
        self._last_bytes = 0
        self._speed_mbs = 0.0

    def update(self, chunk):
        self.downloaded += len(chunk)
        self._hesapla_hiz()
        self.draw()

    def _hesapla_hiz(self):
        now = time.monotonic()
        gecen = now - self._last_time
        if gecen >= 0.1:
            delta = self.downloaded - self._last_bytes
            anlik = delta / gecen / 1024 / 1024
            alpha = 0.3
            self._speed_mbs = (
                anlik if self._speed_mbs == 0.0
                else alpha * anlik + (1 - alpha) * self._speed_mbs
            )
            self._last_time = now
            self._last_bytes = self.downloaded

    def draw(self):
        if self.total <= 0:
            return
        oran = min(self.downloaded / self.total, 1.0)
        yuzde = int(oran * 100)
        dolu = int(38 * oran)
        bos = 38 - dolu
        bar = "█" * dolu + "░" * bos

        mb_i = self.downloaded / 1024 / 1024
        mb_t = self.total / 1024 / 1024
        hiz = f"{self._speed_mbs:.1f} MB/s" if self._speed_mbs > 0 else "..."

        adim = f"{DIM}[{self.mevcut_adim}/{self.toplam_adim}]{RE} " if self.toplam_adim > 1 else ""

        sys.stdout.write(
            f"\r{adim}{C}[↓]{RE} {self.desc} "
            f"[{G}{bar}{RE}] "
            f"{W}{yuzde:3d}%{RE} "
            f"{DIM}{mb_i:.1f}/{mb_t:.1f} MB{RE}  "
            f"{Y}{hiz:>12}{RE}  "
        )
        sys.stdout.flush()

    def finish(self):
        sure = time.monotonic() - self._start_time
        mb = self.downloaded / 1024 / 1024
        hiz = f"{mb/sure:.1f} MB/s" if sure > 0 else "?"
        sys.stdout.write(
            f"\r{G}[✓]{RE} {self.desc}  "
            f"{W}{mb:.1f} MB{RE}, ortalama {Y}{hiz}{RE}      \n"
        )
        sys.stdout.flush()


def adim_goster(adim: int, toplam: int, mesaj: str):
    print(f"  {DIM}[{adim}/{toplam}]{RE} {C}{mesaj}{RE}")


def sure_tahmin(boyut_bayt: int) -> str:
    ort_hiz = 3 * 1024 * 1024
    sure = boyut_bayt / ort_hiz
    if sure < 10: return "~10 saniye"
    elif sure < 60: return f"~{int(sure)} saniye"
    else: return f"~{int(sure/60)} dakika"


# ══════════════════════════════════════════════════════════════════
# DB İŞLEMLERİ
# ══════════════════════════════════════════════════════════════════

def db_yukle() -> Dict:
    try:
        with open(KURULU_DB) as f:
            return json.load(f)
    except Exception:
        return {}


def db_kaydet(db: Dict):
    ensure_dirs()
    tmp = Path(KURULU_DB + ".tmp")
    tmp.write_text(json.dumps(db, indent=2, ensure_ascii=False), encoding="utf-8")
    os.replace(str(tmp), KURULU_DB)


def db_yedekle():
    db = db_yukle()
    yedek = f"{KURULU_DB}.backup.{int(datetime.now().timestamp())}"
    with open(yedek, "w") as f:
        json.dump(db, f, indent=2, ensure_ascii=False)
    return yedek


def db_versiyon_kontrol(name: str, meta: Dict) -> str:
    """Versiyon bilgisi bozuksa dpkg'den al"""
    ver = meta.get("version", "0")
    if not re.match(r'^[0-9]', ver):
        try:
            result = subprocess.run(
                ["dpkg-query", "-W", "-f=${Version}", "--", name],
                capture_output=True, text=True, timeout=5, env=cmd_env()
            )
            if result.returncode == 0 and result.stdout.strip():
                dpkg_ver = result.stdout.strip()
                if re.match(r'^[0-9]', dpkg_ver):
                    meta["version"] = dpkg_ver
                    return dpkg_ver
        except Exception:
            pass
        return "0"
    return ver


# ══════════════════════════════════════════════════════════════════
# PIN İŞLEMLERİ
# ══════════════════════════════════════════════════════════════════

def pin_yukle() -> List[str]:
    try:
        with open(PIN_FILE) as f:
            return json.load(f)
    except Exception:
        return []


def pin_kaydet(pins: List[str]):
    ensure_dirs()
    with open(PIN_FILE, "w") as f:
        json.dump(pins, f, indent=2, ensure_ascii=False)


def pin_ekle(pkgs):
    pins = pin_yukle()
    for p in pkgs:
        if p not in pins:
            pins.append(p)
            basari(f"Pinlendi: {p}")
        else:
            uyari(f"Zaten pinli: {p}")
    pin_kaydet(pins)


def pin_kaldir(pkgs):
    pins = pin_yukle()
    for p in pkgs:
        if p in pins:
            pins.remove(p)
            basari(f"Pin kaldırıldı: {p}")
        else:
            uyari(f"Pinli değil: {p}")
    pin_kaydet(pins)


# ══════════════════════════════════════════════════════════════════
# REPO İŞLEMLERİ
# ══════════════════════════════════════════════════════════════════

def repo_getir(force_refresh=False) -> Optional[Dict]:
    cache_path = Path(INDEX_CACHE)
    if not force_refresh and cache_path.exists():
        age = datetime.now().timestamp() - cache_path.stat().st_mtime
        if age < 3600:
            try:
                with open(cache_path) as f:
                    return json.load(f)
            except Exception:
                pass
    return None


def update():
    bilgi("Repo indexi güncelleniyor...")
    try:
        req = urllib.request.Request(REPO_INDEX, headers={"Accept-Encoding": "gzip"})
        with urllib.request.urlopen(req, timeout=60) as response:
            data = response.read()
            if response.headers.get("Content-Encoding") == "gzip":
                data = gzip.decompress(data)
            ensure_dirs()
            with open(INDEX_CACHE, "wb") as f:
                f.write(data)
            index = json.loads(data)
            basari(f"Repo güncellendi: {index.get('package_count', 0)} paket")
            return 0
    except urllib.error.URLError as e:
        return hata(f"Bağlantı hatası: {e}")
    except json.JSONDecodeError:
        return hata("Repo indexi bozuk.")


# ══════════════════════════════════════════════════════════════════
# YARDIMCI FONKSİYONLAR
# ══════════════════════════════════════════════════════════════════

def sha256(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for b in iter(lambda: f.read(1024 * 1024), b""):
            h.update(b)
    return h.hexdigest()


def boyut_formatla(bayt) -> str:
    try:
        bayt = int(bayt)
    except Exception:
        return str(bayt)
    if bayt < 1024: return f"{bayt} B"
    elif bayt < 1024 ** 2: return f"{bayt/1024:.1f} KB"
    elif bayt < 1024 ** 3: return f"{bayt/1024/1024:.1f} MB"
    return f"{bayt/1024/1024/1024:.1f} GB"


def versiyon_karsilastir(v1: str, v2: str) -> int:
    try:
        if subprocess.run(
            ["dpkg", "--compare-versions", v1, "lt", v2],
            capture_output=True, env=cmd_env()
        ).returncode == 0:
            return -1
        if subprocess.run(
            ["dpkg", "--compare-versions", v1, "gt", v2],
            capture_output=True, env=cmd_env()
        ).returncode == 0:
            return 1
        return 0
    except Exception:
        if v1 < v2: return -1
        if v1 > v2: return 1
        return 0


def dep_temizle(dep: str) -> str:
    dep = dep.split("|", 1)[0].strip()
    dep = re.sub(r"\s*\(.*?\)", "", dep).strip()
    dep = re.sub(r"\s*\[.*?\]", "", dep).strip()
    dep = re.sub(r":[A-Za-z0-9_-]+$", "", dep).strip()
    return dep


def dep_alternatifleri(dep: str) -> List[str]:
    sonuc = []
    for alt in dep.split("|"):
        alt = alt.strip()
        alt = re.sub(r"\s*\(.*?\)", "", alt).strip()
        alt = re.sub(r"\s*\[.*?\]", "", alt).strip()
        alt = re.sub(r":[A-Za-z0-9_-]+$", "", alt).strip()
        if alt and gecerli_paket_adi(alt):
            sonuc.append(alt)
    return sonuc


def bagimlilik_yoksay(name: str) -> bool:
    if name in IGNORED_DEPS:
        return True
    return any(name.endswith(s) for s in IGNORED_DEP_SUFFIXES)


def paket_kategorisi(name: str, info: Dict) -> str:
    cls = info.get("class", "")
    if cls:
        return cls
    section = info.get("section", "").lower()
    if section in LIB_SECTIONS or name.startswith("lib"):
        return "base"
    if is_tool_package(name):
        return "tool"
    return "app"


# ══════════════════════════════════════════════════════════════════
# SİSTEM / DPKG KONTROL
# ══════════════════════════════════════════════════════════════════

_dpkg_cache: Dict[str, bool] = {}


def _dpkg_kurulu_mu(name: str) -> bool:
    if name in _dpkg_cache:
        return _dpkg_cache[name]
    try:
        result = subprocess.run(
            ["dpkg-query", "-W", "-f=${Status}", "--", name],
            capture_output=True, text=True, timeout=5, env=cmd_env()
        )
        kurulu = result.returncode == 0 and "install ok installed" in result.stdout
        _dpkg_cache[name] = kurulu
        return kurulu
    except Exception:
        _dpkg_cache[name] = False
        return False


def _dpkg_provides_kontrol(name: str) -> bool:
    try:
        result = subprocess.run(
            ["dpkg-query", "-W", "-f=${Package}\t${Provides}\t${Status}\n"],
            capture_output=True, text=True, timeout=10, env=cmd_env()
        )
        if result.returncode != 0:
            return False
        for line in result.stdout.splitlines():
            parts = line.strip().split("\t")
            if len(parts) < 3:
                continue
            pkg_name, provides_raw, status = parts[0], parts[1], parts[2]
            if "install ok installed" not in status:
                continue
            if provides_raw:
                for prov in provides_raw.split(","):
                    prov_name = prov.strip().split(" ", 1)[0].strip()
                    if prov_name == name:
                        _dpkg_cache[name] = True
                        return True
        return False
    except Exception:
        return False


def sistemde_paket_var(name: str) -> bool:
    if bagimlilik_yoksay(name):
        return True
    if shutil.which(name):
        return True
    if _dpkg_kurulu_mu(name):
        return True
    if not name.endswith("t64"):
        if _dpkg_kurulu_mu(name + "t64"):
            return True
    if _dpkg_provides_kontrol(name):
        return True
    try:
        result = subprocess.run(
            ["dpkg-query", "-W", "-f=${Package}\t${Status}\n"],
            capture_output=True, text=True, timeout=10, env=cmd_env()
        )
        if result.returncode == 0:
            for line in result.stdout.splitlines():
                parts = line.strip().split("\t")
                if len(parts) < 2:
                    continue
                if "install ok installed" not in parts[1]:
                    continue
                if parts[0].startswith(name) and parts[0] != name:
                    _dpkg_cache[name] = True
                    return True
    except Exception:
        pass
    return False


def dep_karsilaniyor_mu(dep_expr: str, db: Dict) -> bool:
    for alt in dep_alternatifleri(dep_expr):
        if bagimlilik_yoksay(alt):
            return True
        if alt in db:
            return True
        for pkg_name, meta in db.items():
            if alt in meta.get("provides", []):
                return True
        if sistemde_paket_var(alt):
            return True
    name = dep_temizle(dep_expr)
    if name and bagimlilik_yoksay(name):
        return True
    if name and sistemde_paket_var(name):
        return True
    return False


def dep_karsilaniyor_mu_haric(dep_expr: str, db: Dict, haric: Set[str]) -> bool:
    for alt in dep_alternatifleri(dep_expr):
        if bagimlilik_yoksay(alt):
            return True
        for pkg_name, meta in db.items():
            if pkg_name in haric:
                continue
            if pkg_name == alt or alt in meta.get("provides", []):
                return True
        if sistemde_paket_var(alt):
            return True
    name = dep_temizle(dep_expr)
    if name and bagimlilik_yoksay(name):
        return True
    if name and sistemde_paket_var(name):
        return True
    return False


def secili_kurulu_provider(dep_expr: str, db: Dict) -> Optional[str]:
    for alt in dep_alternatifleri(dep_expr):
        if alt in db:
            return alt
        for pkg_name, meta in db.items():
            if alt in meta.get("provides", []):
                return pkg_name
    return None


def cozum_mesaji_uret(name: str, gerekli: Optional[str] = None) -> str:
    satirlar = []
    if gerekli:
        satirlar.append(f"Bağımlılık bulunamadı: {name}")
        satirlar.append(f"    Gerekli: {gerekli}")
    else:
        satirlar.append(f"Paket bulunamadı: {name}")
    satirlar.append("     nux repo'sunda yok")
    satirlar.append("")
    satirlar.append(f"  {Y}Çözüm:{RE}")
    satirlar.append("    1) Paket adını kontrol edin: nux search <kelime>")
    satirlar.append(f"    2) Forum: {C}https://forum.nucoralinux.com.tr{RE}")
    if gerekli:
        satirlar.append(f"    3) Eksik paket: {name} (isteyen: {gerekli})")
    return "\n".join(satirlar)


# ══════════════════════════════════════════════════════════════════
# SİSTEM GÜNCELLEME
# ══════════════════════════════════════════════════════════════════

def sistem_guncelle():
    komutlar = [
        ["ldconfig"],
        ["update-desktop-database", "/usr/share/applications"],
        ["gtk-update-icon-cache", "-f", "-t", "/usr/share/icons/hicolor"],
        ["update-mime-database", "/usr/share/mime"],
        ["glib-compile-schemas", "/usr/share/glib-2.0/schemas"],
    ]
    for komut in komutlar:
        try:
            subprocess.run(komut, capture_output=True, text=True,
                          timeout=30, env=cmd_env())
        except (FileNotFoundError, Exception):
            pass


# ══════════════════════════════════════════════════════════════════
# TAR GÜVENLİK
# ══════════════════════════════════════════════════════════════════

def safe_extract(tar: tarfile.TarFile, path: Path):
    base = os.path.realpath(path)
    for member in tar.getmembers():
        target = os.path.realpath(os.path.join(path, member.name))
        if not target.startswith(base + os.sep) and target != base:
            raise Exception(f"Güvensiz tar yolu: {member.name}")
    tar.extractall(path)


# ══════════════════════════════════════════════════════════════════
# BENZERLİK / ÖNERİ
# ══════════════════════════════════════════════════════════════════

def _benzerlik_skoru(sorgu: str, aday: str) -> int:
    s = sorgu.lower()
    a = aday.lower()
    if a.startswith(s): return 50
    if s in a: return 30
    idx = 0
    for ch in s:
        pos = a.find(ch, idx)
        if pos == -1: return 0
        idx = pos + 1
    return 10


def benzer_paket_oner(sorgu: str, index: Dict, limit=5) -> List[str]:
    paketler = index.get("packages", {})
    skorlar = []
    for ad in paketler:
        skor = _benzerlik_skoru(sorgu, ad)
        if skor > 0:
            skorlar.append((skor, ad))
    skorlar.sort(key=lambda x: (-x[0], x[1]))
    return [ad for _, ad in skorlar[:limit]]


def _oneri_goster(sorgu, index):
    adaylar = benzer_paket_oner(sorgu, index)
    if not adaylar:
        print(renkli(f"\n  '{sorgu}' için benzer paket bulunamadı.\n", Y))
        return
    print(renkli(f"\n  '{sorgu}' bulunamadı. Benzer paketler:\n", Y))
    for i, ad in enumerate(adaylar, 1):
        latest = index["packages"][ad].get("latest", "")
        desc = ""
        if latest:
            desc = index["packages"][ad]["versions"][latest].get("description", "")[:50]
        print(f"    {C}{i}.{RE} {G}{ad:30}{RE}  {desc}")
    print(renkli(f"\n  Kurmak için: sudo nux install <paket-adı>\n", C))


# ══════════════════════════════════════════════════════════════════
# SCRIPT RUNNER
# ══════════════════════════════════════════════════════════════════

def maintscript_env(pkg_name, script_name, meta=None):
    env = cmd_env()
    env["DPKG_MAINTSCRIPT_PACKAGE"] = pkg_name
    env["DPKG_MAINTSCRIPT_NAME"] = script_name
    env["DPKG_ADMINDIR"] = "/var/lib/dpkg"
    if meta:
        arch = meta.get("architecture")
        if arch:
            env["DPKG_MAINTSCRIPT_ARCH"] = arch
    return env


def run_pkg_script(script, pkg_name, script_name, args=None, meta=None, fatal=True):
    if not script.exists():
        return True
    args = args or []
    try:
        result = subprocess.run(
            ["bash", str(script)] + args,
            capture_output=True, text=True,
            cwd=str(script.parent),
            env=maintscript_env(pkg_name, script_name, meta),
            timeout=300
        )
    except subprocess.TimeoutExpired:
        if fatal: hata(f"{script_name} zaman aşımı ({pkg_name})")
        else: uyari(f"{script_name} zaman aşımı ({pkg_name})")
        return False
    except Exception as e:
        if fatal: hata(f"{script_name} çalıştırılamadı ({pkg_name}): {e}")
        else: uyari(f"{script_name} çalıştırılamadı ({pkg_name}): {e}")
        return False

    if result.returncode != 0:
        msg = result.stderr.strip()[:150] or result.stdout.strip()[:150] or f"çıkış kodu {result.returncode}"
        if fatal: hata(f"{script_name} başarısız ({pkg_name}): {msg}")
        else: uyari(f"{script_name} uyarı ({pkg_name}): {msg}")
        return False
    return True


# ══════════════════════════════════════════════════════════════════
# BAĞIMLILIK ÇÖZÜCÜ
# ══════════════════════════════════════════════════════════════════

class DependencyResolver:
    def __init__(self, index: Dict, db: Dict):
        self.index = index or {}
        self.db = db or {}
        self.packages = self.index.get("packages", {})

    def get_package_info(self, name: str) -> Optional[Dict]:
        if name in self.packages:
            pkg = self.packages[name]
            latest = pkg.get("latest")
            if latest and latest in pkg.get("versions", {}):
                return pkg["versions"][latest]
        return None

    def installed_provider(self, name):
        if name in self.db:
            return name
        for pkg_name, meta in self.db.items():
            if name in meta.get("provides", []):
                return pkg_name
        return None

    def repo_provider(self, name):
        if name in self.packages:
            return name
        for pkg_name, pdata in self.packages.items():
            latest = pdata.get("latest")
            if not latest:
                continue
            info = pdata.get("versions", {}).get(latest, {})
            if name in info.get("provides", []):
                return pkg_name
        return None

    def satisfies_dep(self, dep):
        return dep_karsilaniyor_mu(dep, self.db)

    def repo_provider_for_dep(self, dep):
        for alt in dep_alternatifleri(dep):
            if bagimlilik_yoksay(alt):
                return None
            if self.installed_provider(alt) or sistemde_paket_var(alt):
                return None
            provider = self.repo_provider(alt)
            if provider:
                return provider
        name = dep_temizle(dep)
        if name and not bagimlilik_yoksay(name):
            if not self.installed_provider(name) and not sistemde_paket_var(name):
                return self.repo_provider(name)
        return None

    def coz(self, pkgs, ziyaret=None):
        if ziyaret is None:
            ziyaret = set()
        kurulacak = []
        bilgiler = {}

        for pkg in pkgs:
            if pkg in ziyaret:
                continue
            ziyaret.add(pkg)
            if pkg in self.db:
                continue
            provider = self.repo_provider(pkg)
            if not provider:
                if sistemde_paket_var(pkg):
                    debug(f"Bağımlılık sistemde: {pkg}")
                    continue
                raise ValueError(cozum_mesaji_uret(pkg))

            info = self.get_package_info(provider)
            if not info:
                raise ValueError(f"Paket bilgisi alınamadı: {provider}")

            for dep in info.get("depends", []):
                if self.satisfies_dep(dep):
                    continue
                dep_provider = self.repo_provider_for_dep(dep)
                if dep_provider:
                    if dep_provider not in self.db:
                        sub_list, sub_info = self.coz([dep_provider], ziyaret)
                        kurulacak.extend(sub_list)
                        bilgiler.update(sub_info)
                elif dep_karsilaniyor_mu(dep, self.db):
                    debug(f"Bağımlılık alternatifle karşılandı: {dep}")
                else:
                    alts = dep_alternatifleri(dep)
                    dep_name = alts[0] if alts else dep_temizle(dep)
                    raise ValueError(cozum_mesaji_uret(dep_name, provider))

            for conflict in info.get("conflicts", []):
                cname = dep_temizle(conflict)
                if self.installed_provider(cname) or sistemde_paket_var(cname):
                    raise ValueError(f"Paket çakışması: {provider} - {cname}")

            if provider not in kurulacak:
                kurulacak.append(provider)
                bilgiler[provider] = info

        return kurulacak, bilgiler


# ══════════════════════════════════════════════════════════════════
# SCRIPT / DOSYA KURULUM
# ══════════════════════════════════════════════════════════════════

def _script_store(name):
    return Path(SCRIPTS_ROOT) / name


def _scripts_kaydet(name, scripts_dir):
    dst = _script_store(name)
    shutil.rmtree(dst, ignore_errors=True)
    if scripts_dir.exists():
        shutil.copytree(scripts_dir, dst, symlinks=True)


def _install_one(src, dest, files_base, tar_members):
    rel_path = str(src.relative_to(files_base))
    member_key = f"files/{rel_path}"

    if src.is_dir():
        dest.mkdir(parents=True, exist_ok=True)
        member = tar_members.get(member_key)
        if member:
            try: os.chmod(dest, member.mode)
            except Exception: pass
        return None

    dest.parent.mkdir(parents=True, exist_ok=True)

    if src.is_symlink():
        target = os.readlink(src)
        if dest.exists() or dest.is_symlink():
            try:
                if dest.is_dir() and not dest.is_symlink():
                    shutil.rmtree(dest)
                else:
                    dest.unlink()
            except Exception:
                pass
        os.symlink(target, dest)
        return str(dest)

    if not src.is_file():
        return None

    if str(dest) == "/usr/lib/nux/nux.py":
        tmp_dest = dest.with_suffix(".new")
        shutil.copy2(src, tmp_dest)
        member = tar_members.get(member_key)
        if member:
            try: os.chmod(tmp_dest, member.mode)
            except Exception: pass
        tmp_dest.replace(dest)
        return str(dest)

    shutil.copy2(src, dest)
    member = tar_members.get(member_key)
    if member:
        try: os.chmod(dest, member.mode)
        except Exception: pass
        try: os.lchown(dest, member.uid, member.gid)
        except Exception: pass
    return str(dest)


def _pkg_kur(name, info, db, transaction,
             toplam_adim=1, mevcut_adim=1,
             auto_installed=False):
    dosya_adi = info["filename"]
    cache_path = Path(CACHE_DIR) / Path(dosya_adi).name
    url = f"{REPO_URL}/{dosya_adi}"
    basarili = False

    cache_path.parent.mkdir(parents=True, exist_ok=True)

    adim_goster(1, 4, f"İndiriliyor: {name}")
    if (not cache_path.exists()) or (
        "sha256" in info and sha256(str(cache_path)) != info["sha256"]
    ):
        progress = ProgressBar(int(info.get("size", 0)), name, toplam_adim, mevcut_adim)
        if not indir(url, str(cache_path), name, progress):
            return False
    else:
        bilgi(f"Cache'den kullanılıyor: {name}")

    adim_goster(2, 4, "Checksum doğrulanıyor...")
    if "sha256" in info:
        pkg_sha = sha256(str(cache_path))
        if pkg_sha != info["sha256"]:
            hata(f"Checksum uyuşmazlığı: {name}")
            cache_path.unlink(missing_ok=True)
            return False
        basari(f"SHA256 doğrulandı: {pkg_sha[:16]}...")

    try:
        with tempfile.TemporaryDirectory() as tmp_str:
            tmp = Path(tmp_str)
            tar_members = {}

            with tarfile.open(str(cache_path), "r:*") as tar:
                for member in tar.getmembers():
                    tar_members[member.name] = member
                safe_extract(tar, tmp)

            meta_path = tmp / "metadata.json"
            if not meta_path.exists():
                return hata(f"metadata.json bulunamadı: {name}")

            with open(meta_path) as f:
                meta = json.load(f)

            for dep in meta.get("pre_depends", []):
                if not dep_karsilaniyor_mu(dep, db):
                    return hata(f"Pre-depends karşılanmadı: {dep}")

            adim_goster(3, 4, f"Kuruluyor: {name}")

            eski_meta = db.get(name, {})
            eski_versiyon = eski_meta.get("version")

            pre = tmp / "scripts" / "pre-install.sh"
            if pre.exists():
                bilgi("Pre-install çalıştırılıyor...")
                pre_args = ["upgrade", eski_versiyon] if eski_versiyon else ["install"]
                if not run_pkg_script(pre, name, "preinst", pre_args, meta=meta):
                    return False

            conffiles = []
            conf_path = tmp / "control" / "conffiles"
            if conf_path.exists():
                conffiles = [l.strip() for l in open(conf_path) if l.strip()]

            files_dir = tmp / "files"
            if not files_dir.exists():
                files_dir = tmp

            _scripts_kaydet(name, tmp / "scripts")

            kurulu_dosyalar = []
            sha_map = {}
            eski_conffiles = {}

            for src in sorted(files_dir.rglob("*")):
                rel = src.relative_to(files_dir)
                rel_str = str(rel)
                if rel_str in ("metadata.json",):
                    continue
                if rel_str.startswith("scripts/") or rel_str.startswith("control/"):
                    continue

                dest = Path("/") / rel
                dest_str = str(dest)

                is_conf = any(dest_str == c or dest_str.endswith(c) for c in conffiles)
                if is_conf and dest.exists() and not dest.is_symlink():
                    try:
                        eski_conffiles[dest_str] = dest.read_bytes()
                    except Exception:
                        pass

                kurulan = _install_one(src, dest, files_dir, tar_members)
                if kurulan:
                    kurulu_dosyalar.append(kurulan)
                    if src.is_file() and not src.is_symlink():
                        sha_map[kurulan] = sha256(str(src))

            post = tmp / "scripts" / "post-install.sh"
            if post.exists():
                bilgi("Post-install çalıştırılıyor...")
                post_args = ["configure", eski_versiyon] if eski_versiyon else ["configure"]
                run_pkg_script(post, name, "postinst", post_args, meta=meta, fatal=False)

            adim_goster(4, 4, "Veritabanı güncelleniyor...")

            meta["files"] = kurulu_dosyalar
            meta["sha256_files"] = sha_map
            meta["install_date"] = datetime.now().isoformat()
            meta["conffiles"] = conffiles
            meta["conffiles_backup"] = {k: v.hex() for k, v in eski_conffiles.items()}

            if eski_meta:
                meta["auto_installed"] = eski_meta.get("auto_installed", False)
            else:
                meta["auto_installed"] = auto_installed
            meta["install_reason"] = "dependency" if meta["auto_installed"] else "manual"

            # class koruması
            if "class" not in meta:
                if is_tool_package(name):
                    meta["class"] = "tool"
                elif auto_installed:
                    meta["class"] = "base"
                else:
                    meta["class"] = "app"

            db[name] = meta
            transaction.append(("INSTALL", name, meta))
            basarili = True

    except Exception as e:
        hata(f"Kurulum hatası ({name}): {e}")
        if VERBOSE:
            import traceback
            print(traceback.format_exc())
        return False
    finally:
        if basarili and cache_path.exists():
            cache_path.unlink(missing_ok=True)

    return True


def indir(url, hedef, paket_adi="", progress=None):
    hedef_path = Path(hedef)
    hedef_path.parent.mkdir(parents=True, exist_ok=True)

    resume = hedef_path.stat().st_size if hedef_path.exists() else 0
    headers = {}
    if resume > 0:
        headers["Range"] = f"bytes={resume}-"

    try:
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=60) as response:
            total = int(response.headers.get("Content-Length", 0))
            if resume > 0:
                total += resume

            if progress is None:
                progress = ProgressBar(total, paket_adi)

            progress.total = total
            mode = "ab" if resume > 0 else "wb"
            progress.downloaded = resume

            with open(hedef, mode) as f:
                while True:
                    chunk = response.read(1024 * 64)
                    if not chunk:
                        break
                    f.write(chunk)
                    progress.update(chunk)

            progress.finish()
        return True
    except urllib.error.URLError as e:
        hata(f"İndirme hatası: {e}")
        return False
    except Exception as e:
        hata(f"Beklenmeyen hata: {e}")
        return False


# ══════════════════════════════════════════════════════════════════
# TRANSACTION
# ══════════════════════════════════════════════════════════════════

def rollback_transaction(transaction, db):
    uyari("Rollback yapılıyor...")
    for action, name, meta in reversed(transaction):
        if action == "INSTALL":
            for f in meta.get("files", []):
                try: Path(f).unlink(missing_ok=True)
                except Exception: pass
            shutil.rmtree(_script_store(name), ignore_errors=True)
            db.pop(name, None)
    db_kaydet(db)
    basari("Rollback tamamlandı")


# ══════════════════════════════════════════════════════════════════
# INSTALL
# ══════════════════════════════════════════════════════════════════

def install(pkgs, dry_run=False):
    if root_kontrol() and not dry_run:
        return 1
    if not pkgs:
        return hata("Paket adı belirtmelisiniz: nux install <paket>")

    # local-install kontrolü
    if len(pkgs) == 1 and (pkgs[0].endswith(".nux") or os.path.isfile(pkgs[0])):
        return local_install(pkgs[0])

    if not dry_run:
        try: acquire_lock()
        except LockError as e: return hata(str(e))

    try:
        db = db_yukle()
        index = repo_getir()
        if index is None:
            update()
            index = repo_getir()
        if index is None:
            return hata("Repo erişilemiyor.")

        repo_paketleri = index.get("packages", {})
        eksik = []

        for pkg in pkgs:
            if pkg in db:
                if db[pkg].get("auto_installed", False):
                    db[pkg]["auto_installed"] = False
                    db[pkg]["install_reason"] = "manual"
                    db_kaydet(db)
                    basari(f"Manuel olarak işaretlendi: {pkg}")
                else:
                    bilgi(f"Zaten kurulu: {pkg}")
                continue
            if pkg in repo_paketleri:
                continue
            if sistemde_paket_var(pkg):
                bilgi(f"Zaten sistemde kurulu: {pkg}")
                continue
            eksik.append(pkg)

        if eksik:
            for pkg in eksik:
                _oneri_goster(pkg, index)
            return 1

        yedek_db = db_yedekle() if not dry_run else None
        transaction = []

        try:
            resolver = DependencyResolver(index, db)
            kurulacak, bilgiler = resolver.coz(pkgs)
        except ValueError as e:
            return hata(str(e))

        yeni = [p for p in kurulacak if p not in db]
        if not yeni:
            bilgi("Kurulacak yeni paket yok.")
            return 0

        manuel_kokler = set()
        for pkg in pkgs:
            provider = resolver.repo_provider(pkg) or pkg
            manuel_kokler.add(provider)

        toplam_boyut = sum(int(bilgiler[p].get("installed_size", 0) or 0) for p in yeni)
        indirme = sum(int(bilgiler[p].get("size", 0) or 0) for p in yeni)

        print(box_header("KURULACAK PAKETLER" if not dry_run else "DRY-RUN — KURULACAK PAKETLER"))
        print()

        for p in yeni:
            info = bilgiler[p]
            boyut = int(info.get("installed_size", 0) or 0)
            cls = info.get("class", "base" if p not in manuel_kokler else "app")
            badge = class_badge(cls)
            auto_str = f" {DIM}[auto]{RE}" if p not in manuel_kokler else ""
            print(
                f"  {G}✓{RE} {C}{p:30}{RE} "
                f"{Y}{info.get('version','?'):18}{RE} "
                f"{boyut_formatla(boyut):>8} {badge}{auto_str}"
            )

        print(f"\n  {LINE_H * 58}")
        print(f"  {W}Toplam:{RE} {len(yeni)} paket")
        print(f"  {W}Kurulu boyut:{RE} {boyut_formatla(toplam_boyut)}")
        print(f"  {W}İndirilecek:{RE} {boyut_formatla(indirme)}")
        print(f"  {W}Tahmini süre:{RE} {sure_tahmin(indirme)}")
        print()

        if dry_run:
            bilgi("Dry-run tamamlandı.")
            return 0

        if not onayla("Kurulum yapılsın mı?"):
            return 0

        baslangic = time.monotonic()
        ok = 0
        hatalar = []

        for i, name in enumerate(yeni, 1):
            print(renkli(f"\n{'─'*58}", DIM))
            print(f"  {W}Paket {i}/{len(yeni)}:{RE} {C}{name}{RE}")
            print(renkli(f"{'─'*58}", DIM))

            info = bilgiler[name]

            eksik_deps = []
            for dep in info.get("depends", []):
                if not dep_karsilaniyor_mu(dep, db):
                    eksik_deps.append(dep)

            if eksik_deps:
                uyari(f"Atlandı: {name} (karşılanmayan: {', '.join(eksik_deps[:3])})")
                hatalar.append(name)
                continue

            auto_flag = name not in manuel_kokler

            if _pkg_kur(name, info, db, transaction, len(yeni), i, auto_installed=auto_flag):
                basari(f"✓ {name} kuruldu")
                history_yaz("INSTALL", name, info.get("version"))
                db_kaydet(db)
                ok += 1
            else:
                uyari(f"✗ {name} kurulamadı")
                hatalar.append(name)

        if ok > 0:
            with Spinner("Sistem veritabanları güncelleniyor..."):
                sistem_guncelle()

        sure = time.monotonic() - baslangic
        sure_str = f"{sure:.0f} saniye" if sure < 60 else f"{int(sure)//60}dk {int(sure)%60}s"

        print()
        if ok == len(yeni):
            basari(f"Tüm paketler başarıyla kuruldu ({ok}/{len(yeni)})")
            print(renkli(f"  Kurulum {sure_str}de tamamlandı.", G))
            if yedek_db:
                Path(yedek_db).unlink(missing_ok=True)
            return 0
        else:
            uyari(f"{ok}/{len(yeni)} paket kuruldu")
            if hatalar:
                print(renkli(f"  Hatalı: {', '.join(hatalar)}", R))
            if onayla("Rollback yapılsın mı?"):
                rollback_transaction(transaction, db)
            return 1

    finally:
        if not dry_run:
            release_lock()


# ══════════════════════════════════════════════════════════════════
# LOCAL INSTALL
# ══════════════════════════════════════════════════════════════════

def local_install(dosya_yolu):
    if root_kontrol():
        return 1

    path = Path(dosya_yolu)
    if not path.exists():
        return hata(f"Dosya bulunamadı: {dosya_yolu}")
    if not dosya_yolu.endswith(".nux"):
        return hata("Geçersiz format. .nux uzantılı dosya bekleniyor.")

    try:
        acquire_lock()
    except LockError as e:
        return hata(str(e))

    try:
        db = db_yukle()
        transaction = []

        with tarfile.open(str(path), "r:*") as tar:
            meta_file = tar.extractfile(tar.getmember("metadata.json"))
            meta = json.loads(meta_file.read().decode("utf-8"))

        name = meta.get("name", path.stem)
        version = meta.get("version", "?")
        cls = meta.get("class", "app")

        print(box_header("YEREL KURULUM"))
        print(f"  {G}✓{RE} {C}{name:30}{RE} {Y}{version:18}{RE} {class_badge(cls)}")
        print(f"  {DIM}Kaynak: {dosya_yolu}{RE}")
        print()

        if not onayla("Kurulum yapılsın mı?"):
            return 0

        cache_path = Path(CACHE_DIR) / path.name
        shutil.copy2(path, cache_path)

        meta["sha256"] = sha256(str(cache_path))
        meta["filename"] = f"local/{path.name}"

        if _pkg_kur(name, meta, db, transaction, auto_installed=False):
            basari(f"✓ {name} kuruldu (yerel)")
            history_yaz("LOCAL_INSTALL", name, version)
            db_kaydet(db)
            sistem_guncelle()
            return 0
        else:
            return hata(f"{name} kurulamadı")
    finally:
        release_lock()


# ══════════════════════════════════════════════════════════════════
# REMOVE
# ══════════════════════════════════════════════════════════════════

def remove(pkgs, purge=False):
    if root_kontrol():
        return 1
    if not pkgs:
        return hata("Paket adı belirtmelisiniz.")

    try:
        acquire_lock()
    except LockError as e:
        return hata(str(e))

    try:
        db = db_yukle()
        pins = pin_yukle()

        silinecek = []
        for name in pkgs:
            if name not in db:
                uyari(f"Kurulu değil: {name}")
                continue
            if name in pins:
                uyari(f"Pinli paket: {name}")
                continue
            if name in PROTECTED_PACKAGES:
                uyari(f"Korumalı paket: {name}")
                continue
            if db[name].get("protected", False):
                uyari(f"Korumalı paket: {name}")
                continue
            silinecek.append(name)

        if not silinecek:
            return 0

        silinecek_set = set(silinecek)

        for pkg_name, pkg_data in db.items():
            if pkg_name in silinecek_set:
                continue
            for dep_expr in pkg_data.get("depends", []):
                if not dep_karsilaniyor_mu_haric(dep_expr, db, silinecek_set):
                    return hata(
                        f"'{', '.join(sorted(silinecek_set))}' kaldırılırsa "
                        f"'{pkg_name}' bozulur: {dep_expr}"
                    )

        print(box_header(f"SİLİNECEK PAKETLER ({len(silinecek)})"))
        for p in silinecek:
            cls = paket_sinifi(db[p])
            print(f"  {R}✗{RE} {p} {Y}{db[p].get('version','?')}{RE} {class_badge(cls)}")
        print()

        if not onayla("Silme işlemi yapılsın mı?"):
            return 0

        for name in silinecek:
            meta = db[name]
            script_dir = _script_store(name)

            pre = script_dir / "pre-remove.sh"
            if pre.exists():
                run_pkg_script(pre, name, "prerm", ["remove"], meta=meta, fatal=False)

            for f_str in meta.get("files", []):
                try:
                    p = Path(f_str)
                    if not p.exists() and not p.is_symlink():
                        continue
                    if not purge and any(
                        f_str == c or f_str.endswith(c)
                        for c in meta.get("conffiles", [])
                    ):
                        continue
                    p.unlink(missing_ok=True)
                    try: p.parent.rmdir()
                    except OSError: pass
                except Exception:
                    pass

            post = script_dir / "post-remove.sh"
            if post.exists():
                run_pkg_script(post, name, "postrm",
                              ["purge" if purge else "remove"],
                              meta=meta, fatal=False)

            shutil.rmtree(script_dir, ignore_errors=True)
            del db[name]
            db_kaydet(db)
            history_yaz("REMOVE", name, meta.get("version"))
            basari(f"Silindi: {name}")

        sistem_guncelle()
        return 0
    finally:
        release_lock()


# ══════════════════════════════════════════════════════════════════
# UPGRADE — class=base paketleri GÖSTERİLMEZ
# ══════════════════════════════════════════════════════════════════

def upgrade():
    if root_kontrol():
        return 1

    try:
        acquire_lock()
    except LockError as e:
        return hata(str(e))

    try:
        db = db_yukle()
        index = repo_getir()
        if index is None:
            update()
            index = repo_getir()

        pins = pin_yukle()
        resolver = DependencyResolver(index, db)
        guncellenecek = []
        nux_updated = False

        for name, meta in db.items():
            if name in pins:
                continue

            # *** v4.1: base paketleri upgrade'den gizle ***
            cls = paket_sinifi(meta)
            if cls == "base":
                continue

            info = resolver.get_package_info(name)
            if info:
                mevcut = db_versiyon_kontrol(name, meta)
                yeni = info.get("version", "0")
                cmp = versiyon_karsilastir(yeni, mevcut)
                if cmp < 0:
                    uyari(f"{name} için downgrade engellendi ({mevcut} > {yeni})")
                    continue
                if cmp > 0:
                    guncellenecek.append((name, mevcut, yeni, info))

        if not guncellenecek:
            basari("Tüm paketler güncel.")
            return 0

        print(box_header(f"GÜNCELLENECEK PAKETLER ({len(guncellenecek)})"))
        for name, eski, yeni, info in guncellenecek:
            cls = info.get("class", paket_sinifi(db.get(name, {})))
            badge = class_badge(cls)
            print(f"  {C}{name:30}{RE} {Y}{eski:15}{RE} → {G}{yeni}{RE} {badge}")
        print()

        if not onayla("Güncelleme yapılsın mı?"):
            return 0

        for name, eski, yeni, info in guncellenecek:
            bilgi(f"Güncelleniyor: {name}")
            eski_meta = db[name]
            for f in eski_meta.get("files", []):
                try:
                    if not any(str(f).endswith(c) for c in eski_meta.get("conffiles", [])):
                        Path(f).unlink(missing_ok=True)
                except Exception:
                    pass

            transaction = []
            if _pkg_kur(name, info, db, transaction):
                basari(f"Güncellendi: {name}")
                history_yaz("UPGRADE", name, f"{eski} → {yeni}")
                db_kaydet(db)
                if name == "nux":
                    nux_updated = True
            else:
                uyari(f"Başarısız: {name}")

        sistem_guncelle()

        if nux_updated:
            basari("nux güncellendi. Yeniden başlatılıyor...")
            release_lock()
            os.execv("/usr/bin/nux", ["nux"] + sys.argv[1:])

        return 0
    finally:
        release_lock()


# ══════════════════════════════════════════════════════════════════
# REINSTALL
# ══════════════════════════════════════════════════════════════════

def reinstall(pkgs):
    if root_kontrol():
        return 1
    if not pkgs:
        return hata("Paket adı belirtmelisiniz.")

    try:
        acquire_lock()
    except LockError as e:
        return hata(str(e))

    try:
        db = db_yukle()
        index = repo_getir()
        if index is None:
            update()
            index = repo_getir()

        transaction = []
        for name in pkgs:
            resolver = DependencyResolver(index, db)
            info = resolver.get_package_info(name)
            if not info:
                uyari(f"Repo'da bulunamadı: {name}")
                continue

            if name in db:
                eski_meta = db[name]
                for f in eski_meta.get("files", []):
                    try:
                        if not any(str(f).endswith(c) for c in eski_meta.get("conffiles", [])):
                            Path(f).unlink(missing_ok=True)
                    except Exception:
                        pass

            cache_path = Path(CACHE_DIR) / Path(info["filename"]).name
            cache_path.unlink(missing_ok=True)

            auto = db.get(name, {}).get("auto_installed", False)
            if _pkg_kur(name, info, db, transaction, auto_installed=auto):
                basari(f"✓ {name} yeniden kuruldu")
                history_yaz("REINSTALL", name, info.get("version"))
                db_kaydet(db)
            else:
                hata(f"{name} yeniden kurulamadı")

        sistem_guncelle()
        return 0
    finally:
        release_lock()


# ══════════════════════════════════════════════════════════════════
# NUX DOCTOR — Sistem sağlık kontrolü
# ══════════════════════════════════════════════════════════════════

def doctor():
    print(box_header("NUX SİSTEM SAĞLIK KONTROLÜ"))
    print()

    checks = []

    # 1. DB kontrolü
    db = db_yukle()
    invalid = gecersiz_db_kayitlari(db)
    if invalid:
        checks.append((False, f"Veritabanı: {len(invalid)} bozuk kayıt"))
    else:
        checks.append((True, f"Veritabanı: {len(db)} paket, sağlıklı"))

    # 2. Repo erişimi
    try:
        start = time.monotonic()
        req = urllib.request.Request(REPO_INDEX)
        with urllib.request.urlopen(req, timeout=10) as r:
            r.read()
        ms = (time.monotonic() - start) * 1000
        checks.append((True, f"Repo erişimi: {ms:.0f}ms"))
    except Exception as e:
        checks.append((False, f"Repo erişimi: Başarısız ({e})"))

    # 3. Cache
    cache = Path(CACHE_DIR)
    cache_files = list(cache.rglob("*.nux")) + list(cache.rglob("*.partial"))
    cache_size = sum(f.stat().st_size for f in cache_files if f.exists())
    checks.append((True, f"Cache: {len(cache_files)} dosya, {boyut_formatla(cache_size)}"))

    # 4. Lock
    if Path(LOCK_FILE).exists():
        pid = Path(LOCK_FILE).read_text().strip()
        if pid and Path(f"/proc/{pid}").exists():
            checks.append((False, f"Lock: Başka işlem çalışıyor (PID: {pid})"))
        else:
            checks.append((False, "Lock: Eski lock dosyası var (temizlenebilir)"))
    else:
        checks.append((True, "Lock: Temiz"))

    # 5. Eksik dosya kontrolü
    eksik_dosya = 0
    for name, meta in db.items():
        if not gecerli_paket_adi(name):
            continue
        for dosya in meta.get("files", [])[:10]:  # ilk 10 dosyayı kontrol et
            if not Path(dosya).exists() and not Path(dosya).is_symlink():
                eksik_dosya += 1
    if eksik_dosya:
        checks.append((False, f"Dosya bütünlüğü: {eksik_dosya} eksik dosya"))
    else:
        checks.append((True, "Dosya bütünlüğü: Sağlıklı"))

    # 6. Orphan kontrolü
    has_auto = any("auto_installed" in m for m in db.values())
    if has_auto:
        orphan_count = sum(
            1 for name, meta in db.items()
            if meta.get("auto_installed", False)
            and gecerli_paket_adi(name)
        )
        if orphan_count > 0:
            checks.append((None, f"Orphan paketler: {orphan_count} adet"))
        else:
            checks.append((True, "Orphan paketler: Yok"))
    else:
        checks.append((None, "Orphan: Eski DB formatı, auto bilgisi yok"))

    # 7. Class dağılımı
    class_counts = {"app": 0, "base": 0, "tool": 0}
    for meta in db.values():
        cls = paket_sinifi(meta)
        class_counts[cls] = class_counts.get(cls, 0) + 1
    checks.append((True,
        f"Sınıf dağılımı: {class_counts.get('app',0)} app, "
        f"{class_counts.get('base',0)} base, "
        f"{class_counts.get('tool',0)} tool"))

    # Sonuçları göster
    for status, msg in checks:
        if status is True:
            icon = f"{G}✓{RE}"
        elif status is False:
            icon = f"{R}✗{RE}"
        else:
            icon = f"{Y}!{RE}"
        print(f"  {icon} {msg}")

    print()

    hatali = sum(1 for s, _ in checks if s is False)
    if hatali == 0:
        basari("Sistem sağlıklı!")
    else:
        uyari(f"{hatali} sorun tespit edildi.")
        print(f"\n  {DIM}Sorunları çözmek için:{RE}")
        if invalid:
            print(f"    {C}nux db-clean{RE}")
        if eksik_dosya:
            print(f"    {C}nux repair{RE}")
    print()
    return 0


# ══════════════════════════════════════════════════════════════════
# NUX WHY — Paket neden kurulu?
# ══════════════════════════════════════════════════════════════════

def why(name):
    db = db_yukle()

    if name not in db:
        return hata(f"Kurulu değil: {name}")

    meta = db[name]
    cls = paket_sinifi(meta)
    auto = meta.get("auto_installed", False)
    reason = meta.get("install_reason", "bilinmiyor")
    install_date = meta.get("install_date", "?")

    print(f"\n  {W}{name}{RE} kurulu çünkü:")
    print()

    if not auto:
        print(f"    → {G}Manuel olarak kurulmuş{RE}")
    else:
        print(f"    → {Y}Bağımlılık olarak kurulmuş (otomatik){RE}")

    # Hangi paketler buna bağımlı
    bagimlilar = []
    for pkg_name, pkg_data in db.items():
        if pkg_name == name:
            continue
        if not gecerli_paket_adi(pkg_name):
            continue
        for dep in pkg_data.get("depends", []):
            cleaned = dep_temizle(dep)
            if cleaned == name:
                bagimlilar.append(pkg_name)
                break

    if bagimlilar:
        print(f"\n    {C}Bağımlı paketler:{RE}")
        for p in sorted(bagimlilar):
            pcls = paket_sinifi(db.get(p, {}))
            print(f"      {TREE_T}{TREE_DASH} {p} {class_badge(pcls)}")

    print(f"\n    {DIM}Sınıf      : {cls}{RE}")
    print(f"    {DIM}Neden      : {reason}{RE}")
    print(f"    {DIM}Tarih      : {install_date[:19]}{RE}")
    print(f"    {DIM}Versiyon   : {meta.get('version', '?')}{RE}")
    print()
    return 0


# ══════════════════════════════════════════════════════════════════
# NUX TREE — Dependency ağacı
# ══════════════════════════════════════════════════════════════════

def tree(name, max_depth=4):
    index = repo_getir()
    db = db_yukle()
    if not index:
        update()
        index = repo_getir()

    resolver = DependencyResolver(index, db)
    info = resolver.get_package_info(name)

    if not info and name not in db:
        return hata(f"Paket bulunamadı: {name}")

    if not info:
        info = db.get(name, {})

    cls = info.get("class", paket_sinifi(db.get(name, {})))
    version = info.get("version", db.get(name, {}).get("version", "?"))

    print(f"\n  {W}{name}{RE} {Y}{version}{RE} {class_badge(cls)}")

    def _tree_print(pkg_info, prefix="  ", depth=0, visited=None):
        if visited is None:
            visited = set()
        if depth >= max_depth:
            return

        deps = pkg_info.get("depends", [])
        for i, dep in enumerate(deps):
            dep_name = dep_temizle(dep)
            if not dep_name:
                continue

            is_last = (i == len(deps) - 1)
            connector = f"{TREE_L}{TREE_DASH}" if is_last else f"{TREE_T}{TREE_DASH}"
            child_prefix = "    " if is_last else f"  {TREE_I} "

            # Sınıf ve durum
            if dep_name in db:
                dep_cls = paket_sinifi(db[dep_name])
                status = f"{G}●{RE}"
            elif sistemde_paket_var(dep_name):
                dep_cls = "base"
                status = f"{B}●{RE}"
            else:
                dep_cls = "?"
                status = f"{R}○{RE}"

            badge = class_badge(dep_cls) if dep_cls != "?" else f"{DIM}[?]{RE}"

            if dep_name in visited:
                print(f"{prefix}{connector} {status} {dep_name} {badge} {DIM}(döngü){RE}")
                continue

            print(f"{prefix}{connector} {status} {dep_name} {badge}")

            visited.add(dep_name)

            # Alt ağaç
            dep_info = resolver.get_package_info(dep_name)
            if not dep_info and dep_name in db:
                dep_info = db[dep_name]
            if dep_info and depth < max_depth - 1:
                _tree_print(dep_info, prefix + child_prefix, depth + 1, visited)

    _tree_print(info)
    print(f"\n  {DIM}● kurulu (nux)  ● kurulu (sistem)  ○ kurulu değil{RE}")
    print()
    return 0


# ══════════════════════════════════════════════════════════════════
# NUX SIZE — Disk kullanım raporu
# ══════════════════════════════════════════════════════════════════

def size_report():
    db = db_yukle()
    if not db:
        bilgi("Kurulu paket yok.")
        return 0

    siralama = []
    for name, meta in db.items():
        if not gecerli_paket_adi(name):
            continue
        boyut = int(meta.get("installed_size", 0) or 0)
        if boyut == 0:
            for dosya in meta.get("files", []):
                try:
                    p = Path(dosya)
                    if p.is_file():
                        boyut += p.stat().st_size
                except Exception:
                    pass
        cls = paket_sinifi(meta)
        siralama.append((name, boyut, meta.get("version", "?"), cls))

    siralama.sort(key=lambda x: x[1], reverse=True)
    max_boyut = siralama[0][1] if siralama else 1

    print(box_header("DİSK KULLANIM RAPORU"))
    print()

    # Sınıf bazlı toplam
    class_totals = {"app": 0, "base": 0, "tool": 0}
    for _, boyut, _, cls in siralama:
        class_totals[cls] = class_totals.get(cls, 0) + boyut

    for cls_name, cls_total in sorted(class_totals.items(), key=lambda x: -x[1]):
        if cls_total > 0:
            color = CLASS_COLORS.get(cls_name, W)
            print(f"  {color}{cls_name.upper():>5}{RE}: {W}{boyut_formatla(cls_total)}{RE}")

    print(f"\n  {LINE_H * 58}")
    print()

    # Top 20
    for i, (name, boyut, version, cls) in enumerate(siralama[:20], 1):
        bar_len = int((boyut / max_boyut) * 25) if max_boyut > 0 else 0
        bar = "█" * bar_len + "░" * (25 - bar_len)
        badge = class_badge(cls)
        print(
            f"  {DIM}{i:2}.{RE} {C}{name:25}{RE} "
            f"{G}{bar}{RE} {Y}{boyut_formatla(boyut):>10}{RE} {badge}"
        )

    toplam = sum(b for _, b, _, _ in siralama)
    print(f"\n  {LINE_H * 58}")
    print(f"  {W}Toplam:{RE} {Y}{boyut_formatla(toplam)}{RE} ({len(siralama)} paket)")
    print()
    return 0


# ══════════════════════════════════════════════════════════════════
# NUX EXPORT / IMPORT
# ══════════════════════════════════════════════════════════════════

def export_packages():
    db = db_yukle()
    packages = []
    for name, meta in sorted(db.items()):
        if not gecerli_paket_adi(name):
            continue
        cls = paket_sinifi(meta)
        auto = meta.get("auto_installed", False)
        packages.append(f"{name}\t{meta.get('version','?')}\t{cls}\t{'auto' if auto else 'manual'}")

    output = "\n".join(packages)
    print(output)
    bilgi(f"{len(packages)} paket listelendi.")
    bilgi(f"Dosyaya kaydetmek için: nux export > paketlerim.txt")
    return 0


def import_list(dosya_yolu):
    if root_kontrol():
        return 1

    path = Path(dosya_yolu)
    if not path.exists():
        return hata(f"Dosya bulunamadı: {dosya_yolu}")

    pkgs = []
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                parts = line.split("\t")
                name = parts[0].split()[0]
                if gecerli_paket_adi(name):
                    pkgs.append(name)

    if not pkgs:
        return hata("Dosyada kurulacak paket bulunamadı.")

    bilgi(f"{len(pkgs)} paket bulundu: {dosya_yolu}")
    return install(pkgs)


# ══════════════════════════════════════════════════════════════════
# SNAPSHOT
# ══════════════════════════════════════════════════════════════════

def snapshot_create(name=None):
    if root_kontrol():
        return 1

    ensure_dirs()
    db = db_yukle()

    if not name:
        name = datetime.now().strftime("snap_%Y%m%d_%H%M%S")

    snap_file = Path(SNAPSHOT_DIR) / f"{name}.json"
    if snap_file.exists():
        return hata(f"Snapshot zaten var: {name}")

    snapshot = {
        "name": name,
        "created_at": datetime.now().isoformat(),
        "nux_version": CLIENT_VERSION,
        "package_count": len(db),
        "packages": {
            pkg: {
                "version": meta.get("version", "?"),
                "class": paket_sinifi(meta),
                "auto_installed": meta.get("auto_installed", False),
            }
            for pkg, meta in db.items()
            if gecerli_paket_adi(pkg)
        }
    }

    with open(snap_file, "w") as f:
        json.dump(snapshot, f, indent=2, ensure_ascii=False)

    basari(f"Snapshot oluşturuldu: {name}")
    bilgi(f"  {len(snapshot['packages'])} paket kaydedildi.")
    return 0


def snapshot_list():
    ensure_dirs()
    snaps = sorted(Path(SNAPSHOT_DIR).glob("*.json"))

    if not snaps:
        bilgi("Hiç snapshot yok.")
        return 0

    print(box_header("SNAPSHOT LİSTESİ"))
    print()

    for snap_file in snaps:
        try:
            with open(snap_file) as f:
                data = json.load(f)
            name = data.get("name", snap_file.stem)
            created = data.get("created_at", "?")[:19]
            count = data.get("package_count", 0)
            print(f"  {C}{name:30}{RE} {DIM}{created}{RE}  {W}{count} paket{RE}")
        except Exception:
            print(f"  {R}{snap_file.stem}{RE}  {DIM}(bozuk){RE}")

    print()
    return 0


def snapshot_restore(name):
    if root_kontrol():
        return 1

    snap_file = Path(SNAPSHOT_DIR) / f"{name}.json"
    if not snap_file.exists():
        return hata(f"Snapshot bulunamadı: {name}")

    with open(snap_file) as f:
        snapshot = json.load(f)

    db = db_yukle()
    snap_pkgs = snapshot.get("packages", {})

    # Fark analizi
    to_install = [p for p in snap_pkgs if p not in db]
    to_remove = [p for p in db if p not in snap_pkgs and gecerli_paket_adi(p)]

    print(box_header(f"SNAPSHOT RESTORE: {name}"))
    print()
    if to_install:
        print(f"  {G}Kurulacak:{RE} {len(to_install)} paket")
        for p in to_install[:10]:
            print(f"    {G}+{RE} {p}")
        if len(to_install) > 10:
            print(f"    {DIM}... ve {len(to_install)-10} daha{RE}")
    if to_remove:
        print(f"  {R}Kaldırılacak:{RE} {len(to_remove)} paket")
        for p in to_remove[:10]:
            print(f"    {R}-{RE} {p}")
        if len(to_remove) > 10:
            print(f"    {DIM}... ve {len(to_remove)-10} daha{RE}")

    if not to_install and not to_remove:
        bilgi("Snapshot ile mevcut durum aynı.")
        return 0

    print()
    if not onayla("Bu değişiklikler uygulansin mi?"):
        return 0

    if to_install:
        install(to_install)
    if to_remove:
        remove(to_remove)

    basari(f"Snapshot restore tamamlandı: {name}")
    return 0


# ══════════════════════════════════════════════════════════════════
# BAKIM FONKSİYONLARI
# ══════════════════════════════════════════════════════════════════

def clean():
    if root_kontrol():
        return 1
    cache = Path(CACHE_DIR)
    dosyalar = list(cache.rglob("*.nux")) + list(cache.rglob("*.partial"))
    if not dosyalar:
        bilgi("Önbellek zaten temiz.")
        return 0
    toplam = sum(f.stat().st_size for f in dosyalar if f.exists())
    print(f"\n{len(dosyalar)} dosya, {boyut_formatla(toplam)} temizlenecek.")
    if not onayla("Devam edilsin mi?"):
        return 0
    for f in dosyalar:
        f.unlink(missing_ok=True)
    basari("Önbellek temizlendi.")
    return 0


def autoremove():
    if root_kontrol():
        return 1

    db = db_yukle()
    if not db:
        bilgi("Kurulu paket yok.")
        return 0

    pins = set(pin_yukle())

    invalid_db = gecersiz_db_kayitlari(db)
    if invalid_db:
        hata("Veritabanında geçersiz kayıtlar var.")
        bilgi(f"Önce temizleyin: nux db-clean ({len(invalid_db)} kayıt)")
        return 1

    has_auto = any("auto_installed" in meta for meta in db.values())
    if not has_auto:
        uyari("Eski veritabanı formatı. Auto bilgisi yok.")
        return 0

    korunanlar = {
        name for name, meta in db.items()
        if name in PROTECTED_PACKAGES
        or meta.get("protected", False)
    }

    manuel_kokler = {
        name for name, meta in db.items()
        if not meta.get("auto_installed", False)
    }

    kokler = manuel_kokler | pins | korunanlar
    ulasilan = set()
    kuyruk = list(kokler)

    while kuyruk:
        pkg = kuyruk.pop(0)
        if pkg in ulasilan or pkg not in db:
            continue
        ulasilan.add(pkg)
        for dep_expr in db[pkg].get("depends", []):
            provider = secili_kurulu_provider(dep_expr, db)
            if provider and provider not in ulasilan:
                kuyruk.append(provider)

    orphan = sorted([
        name for name, meta in db.items()
        if meta.get("auto_installed", False)
        and name not in ulasilan
        and name not in pins
        and name not in korunanlar
    ])

    if not orphan:
        bilgi("Gereksiz paket yok.")
        return 0

    print(renkli(f"\nGereksiz paketler ({len(orphan)}):\n", Y))
    for p in orphan:
        cls = paket_sinifi(db[p])
        boyut = boyut_formatla(db[p].get("installed_size", 0))
        print(f"  {Y}✗{RE} {p} {DIM}[auto] {boyut}{RE} {class_badge(cls)}")
    print()

    if not onayla("Silinsin mi?"):
        return 0
    return remove(orphan)


def verify(pkgs=None):
    db = db_yukle()
    kontrol = pkgs if pkgs else [n for n in db if gecerli_paket_adi(n)]
    if not kontrol:
        bilgi("Kurulu paket yok.")
        return 0

    hatali, eksik, bozuk = [], [], []
    for name in kontrol:
        if name not in db:
            uyari(f"Kurulu değil: {name}")
            continue
        for dosya, beklenen in db[name].get("sha256_files", {}).items():
            p = Path(dosya)
            if not p.exists():
                eksik.append((name, dosya))
                hatali.append(name)
            elif not p.is_symlink() and sha256(dosya) != beklenen:
                bozuk.append((name, dosya))
                hatali.append(name)

    if not hatali:
        basari("Tüm dosyalar sağlıklı.")
        return 0

    if eksik:
        print(renkli(f"\nEksik dosyalar ({len(eksik)}):", R))
        for pkg, f in eksik:
            print(f"  {R}{pkg}{RE}: {f}")
    if bozuk:
        print(renkli(f"\nBozuk dosyalar ({len(bozuk)}):", R))
        for pkg, f in bozuk:
            print(f"  {R}{pkg}{RE}: {f}")

    if onayla("Yeniden kurulsun mu?"):
        return reinstall(list(set(hatali)))
    return 1


def repair():
    if root_kontrol():
        return 1
    db = db_yukle()
    if not db:
        bilgi("Kurulu paket yok.")
        return 0

    with Spinner("Sistem taranıyor..."):
        hatali = []
        for name, meta in db.items():
            for dosya, beklenen in meta.get("sha256_files", {}).items():
                p = Path(dosya)
                if not p.exists() or (not p.is_symlink() and sha256(dosya) != beklenen):
                    hatali.append(name)
                    break

    if not hatali:
        basari("Tüm paketler sağlıklı.")
        return 0

    print(f"\n{R}{len(hatali)} sorunlu paket.{RE}")
    if onayla("Yeniden kurulsun mu?"):
        return reinstall(hatali)
    return 1


def db_clean():
    if root_kontrol():
        return 1

    db = db_yukle()
    if not db:
        bilgi("Veritabanı boş.")
        return 0

    invalid = gecersiz_db_kayitlari(db)
    if not invalid:
        basari("Veritabanı temiz.")
        return 0

    print(renkli(f"\nGeçersiz kayıtlar ({len(invalid)}):\n", R))
    for name in invalid[:50]:
        print(f"  {R}✗{RE} {name}")
    if len(invalid) > 50:
        print(f"  ... ve {len(invalid) - 50} tane daha")
    print()

    if not onayla(f"{len(invalid)} geçersiz kayıt silinsin mi?"):
        return 0

    db_yedekle()
    for name in invalid:
        del db[name]
    db_kaydet(db)
    basari(f"{len(invalid)} geçersiz kayıt temizlendi.")
    return 0


# ══════════════════════════════════════════════════════════════════
# SORGULAMA FONKSİYONLARI
# ══════════════════════════════════════════════════════════════════

def list_all():
    index = repo_getir()
    if index is None:
        update()
        index = repo_getir()
    if index is None:
        return hata("Repo erişilemiyor.")

    db = db_yukle()
    pkgs = index.get("packages", {})

    if not pkgs:
        bilgi("Repo boş.")
        return 0

    kategoriler = {"app": {}, "base": {}, "tool": {}}

    for name, pkg_data in pkgs.items():
        if not gecerli_paket_adi(name):
            continue
        latest = pkg_data.get("latest", "?")
        info = pkg_data["versions"].get(latest, {})
        kat = info.get("class", paket_kategorisi(name, info))
        if kat not in kategoriler:
            kat = "app"
        kategoriler[kat][name] = (pkg_data, info)

    print(box_header(f"REPO'DAKİ TÜM PAKETLER ({len(pkgs)})"))

    kat_labels = {
        "app":  (G, "UYGULAMALAR"),
        "tool": (M, "ARAÇLAR"),
        "base": (Y, "SİSTEM TABANI"),
    }

    for kat_key in ["app", "tool", "base"]:
        kat_dict = kategoriler[kat_key]
        if not kat_dict:
            continue
        color, label = kat_labels[kat_key]
        print(f"\n  {color}── {label} ({len(kat_dict)}) ──{RE}")
        print(f"  {DIM}{'─'*60}{RE}")

        for name in sorted(kat_dict.keys()):
            pkg_data, info = kat_dict[name]
            latest = pkg_data.get("latest", "?")
            if name in db:
                cur_ver = db[name].get("version", "?")
                status = f"{Y}↑{RE}" if cur_ver != latest else f"{G}✓{RE}"
            else:
                status = f"{R}○{RE}"
            size = boyut_formatla(info.get("installed_size", 0))
            desc = info.get("description", "")[:32]
            badge = class_badge(kat_key)
            print(f"  {status} {C}{name:25}{RE} {Y}{latest:18}{RE} {size:>8} {badge} {DIM}{desc}{RE}")

    print(f"\n  {G}✓{RE} Kurulu  {Y}↑{RE} Güncellenebilir  {R}○{RE} Kurulu değil")
    print()
    return 0


def search(query):
    index = repo_getir()
    if index is None:
        update()
        index = repo_getir()
    if index is None:
        return hata("Repo erişilemiyor.")

    db = db_yukle()
    q = query.lower()
    sonuclar = []

    for name, pkg in index.get("packages", {}).items():
        if not gecerli_paket_adi(name):
            continue
        latest = pkg.get("latest")
        if not latest:
            continue
        info = pkg["versions"][latest]
        if (q in name.lower()
            or q in info.get("description", "").lower()
            or any(q in p.lower() for p in info.get("provides", []))):
            sonuclar.append((name, info))

    if not sonuclar:
        uyari(f"'{query}' için sonuç bulunamadı.")
        return 0

    print(renkli(f"\n{len(sonuclar)} sonuç:\n", G))
    for name, info in sorted(sonuclar):
        durum = f" {G}[kurulu]{RE}" if name in db else ""
        cls = info.get("class", paket_kategorisi(name, info))
        badge = class_badge(cls)
        print(
            f"  {C}{name}{RE}/{info.get('architecture','amd64')} "
            f"{Y}{info.get('version','?')}{RE}{durum} {badge}"
        )
        if info.get("description"):
            desc = info["description"][:65]
            print(f"     {DIM}{desc}{RE}")
        print()
    return 0


def info(name):
    index = repo_getir()
    db = db_yukle()

    pkg = None
    versions = []

    if index and name in index.get("packages", {}):
        pkg_data = index["packages"][name]
        versions = list(pkg_data.get("versions", {}).keys())
        latest = pkg_data.get("latest")
        if latest:
            pkg = pkg_data["versions"][latest]

    kurulu_meta = db.get(name)
    if not pkg:
        pkg = kurulu_meta
    if not pkg:
        return hata(f"Paket bulunamadı: {name}")

    cls = pkg.get("class", paket_kategorisi(name, pkg))
    badge = class_badge(cls)

    print(box_header(name.upper()))
    print(f"  {'Paket:':<18} {name}")
    print(f"  {'Versiyon:':<18} {pkg.get('version','?')}")
    if kurulu_meta and kurulu_meta.get("version") != pkg.get("version"):
        print(f"  {'Kurulu versiyon:':<18} {kurulu_meta.get('version')} {Y}(güncelleme mevcut!){RE}")
    print(f"  {'Mimari:':<18} {pkg.get('architecture','amd64')}")
    print(f"  {'Sınıf:':<18} {badge}")
    print(f"  {'Açıklama:':<18} {pkg.get('description','N/A')}")
    print(f"  {'Kategori:':<18} {pkg.get('section','misc')}")
    print(f"  {'Öncelik:':<18} {pkg.get('priority','optional')}")
    print(f"  {'Arşiv boyutu:':<18} {boyut_formatla(pkg.get('size',0))}")
    print(f"  {'Kurulu boyutu:':<18} {boyut_formatla(pkg.get('installed_size',0))}")
    print(f"  {'Bakımcı:':<18} {pkg.get('maintainer','N/A')}")
    if pkg.get("homepage"):
        print(f"  {'Web:':<18} {C}{pkg['homepage']}{RE}")
    if pkg.get("protected"):
        print(f"  {'Korumalı:':<18} {G}Evet{RE}")
    if kurulu_meta:
        auto = "auto (bağımlılık)" if kurulu_meta.get("auto_installed") else "manuel"
        print(f"  {'Kurulum tipi:':<18} {auto}")
        print(f"  {'Kurulum tarihi:':<18} {kurulu_meta.get('install_date','?')[:19]}")
        print(f"  {'Dosya sayısı:':<18} {len(kurulu_meta.get('files',[]))}")

    if versions:
        versions_sorted = sorted(versions, key=cmp_to_key(versiyon_karsilastir), reverse=True)
        print(f"\n  {Y}Versiyonlar:{RE} {', '.join(versions_sorted[:5])}")
    if pkg.get("depends"):
        print(f"\n  {Y}Bağımlılıklar:{RE}")
        for d in pkg["depends"]:
            print(f"     {DIM}{d}{RE}")
    if pkg.get("provides"):
        print(f"\n  {M}Sağladıkları:{RE}")
        for p in pkg["provides"]:
            print(f"     {DIM}{p}{RE}")

    if kurulu_meta:
        print(f"\n  {G}[KURULU]{RE}")
    print()
    return 0


def listele(upgradeable=False):
    db = db_yukle()
    pins = pin_yukle()

    if not db:
        bilgi("Kurulu paket yok.")
        return 0

    if upgradeable:
        index = repo_getir()
        if not index:
            update()
            index = repo_getir()
        resolver = DependencyResolver(index, db)
        guncellenebilir = []
        for name, meta in db.items():
            if not gecerli_paket_adi(name):
                continue
            # base paketleri upgrade listesinde gösterme
            if paket_sinifi(meta) == "base":
                continue
            info = resolver.get_package_info(name)
            if info:
                mevcut = db_versiyon_kontrol(name, meta)
                yeni = info.get("version", "0")
                if versiyon_karsilastir(yeni, mevcut) > 0:
                    guncellenebilir.append((name, mevcut, yeni))

        if not guncellenebilir:
            basari("Tüm paketler güncel.")
            return 0

        print(renkli(f"\nGüncellenebilir ({len(guncellenebilir)}):\n", C))
        for name, eski, yeni in sorted(guncellenebilir):
            cls = paket_sinifi(db[name])
            pinli = f" {Y}[PİNLİ]{RE}" if name in pins else ""
            print(f"  {C}{name:30}{RE} {Y}{eski:15}{RE} → {G}{yeni}{RE} {class_badge(cls)}{pinli}")
        print()
    else:
        gecerli = {n: m for n, m in db.items() if gecerli_paket_adi(n)}
        invalid_count = len(db) - len(gecerli)
        print(renkli(f"\nKurulu paketler ({len(gecerli)}):\n", W))
        for name, meta in sorted(gecerli.items()):
            cls = paket_sinifi(meta)
            pinli = f" {Y}[PİNLİ]{RE}" if name in pins else ""
            auto = f" {DIM}[auto]{RE}" if meta.get("auto_installed") else ""
            print(f"  {G}✓{RE} {C}{name:30}{RE} {Y}{meta.get('version','?')}{RE} {class_badge(cls)}{pinli}{auto}")
        if invalid_count > 0:
            print(renkli(f"\n  ⚠ {invalid_count} geçersiz kayıt. 'nux db-clean' ile temizleyin.", Y))
        print()
    return 0


def depends_cmd(name, reverse=False):
    index = repo_getir()
    db = db_yukle()
    if not index:
        update()
        index = repo_getir()

    if not reverse:
        if not index or name not in index.get("packages", {}):
            return hata(f"Paket bulunamadı: {name}")
        info = index["packages"][name]["versions"][index["packages"][name]["latest"]]
        deps = info.get("depends", [])
        if not deps:
            bilgi(f"{name} bağımlılığı yok.")
            return 0
        print(renkli(f"\n{name} bağımlılıkları:\n", W))
        for d in deps:
            dname = dep_temizle(d)
            if dname in db:
                durum = f"{G}[kurulu/nux]{RE}"
            elif sistemde_paket_var(dname):
                durum = f"{B}[kurulu/sistem]{RE}"
            else:
                durum = f"{R}[eksik]{RE}"
            print(f"   {C}{d}{RE} {durum}")
        print()
    else:
        bagimlilar = []
        for pname, pdata in db.items():
            if not gecerli_paket_adi(pname):
                continue
            if name in [dep_temizle(x) for x in pdata.get("depends", [])]:
                bagimlilar.append(pname)
        if not bagimlilar:
            bilgi(f"{name} hiçbir paketin bağımlılığı değil.")
            return 0
        print(renkli(f"\n{name} paketine bağımlı olanlar:\n", W))
        for p in bagimlilar:
            cls = paket_sinifi(db.get(p, {}))
            print(f"   {C}{p}{RE} {class_badge(cls)}")
        print()
    return 0


def policy(name):
    index = repo_getir()
    db = db_yukle()
    if not index or name not in index.get("packages", {}):
        return hata(f"Paket bulunamadı: {name}")

    pkg_data = index["packages"][name]
    kurulu = db.get(name, {})
    cls = kurulu.get("class", "app") if kurulu else "app"

    print(f"\n{name}: {class_badge(cls)}")
    print(f"  Kurulu:  {kurulu.get('version') if kurulu else '(yok)'}")
    print(f"  Aday:    {pkg_data.get('latest')}")
    print(f"\n  Versiyon tablosu:")

    versions_sorted = sorted(
        pkg_data.get("versions", {}).keys(),
        key=cmp_to_key(versiyon_karsilastir), reverse=True
    )
    for vers in versions_sorted:
        marker = "***" if vers == kurulu.get("version") else "   "
        print(f"    {marker} {vers:20} 500 {REPO_URL}")
    print()
    return 0


def count():
    db = db_yukle()
    index = repo_getir()

    kurulu = len([n for n in db if gecerli_paket_adi(n)])
    repoda = 0
    guncellenebilir = 0

    class_counts = {"app": 0, "base": 0, "tool": 0}
    for meta in db.values():
        cls = paket_sinifi(meta)
        class_counts[cls] = class_counts.get(cls, 0) + 1

    if index:
        repoda = index.get("package_count", 0)
        resolver = DependencyResolver(index, db)
        for name, meta in db.items():
            if not gecerli_paket_adi(name):
                continue
            if paket_sinifi(meta) == "base":
                continue
            info = resolver.get_package_info(name)
            if info:
                mevcut = db_versiyon_kontrol(name, meta)
                yeni = info.get("version", "0")
                if versiyon_karsilastir(yeni, mevcut) > 0:
                    guncellenebilir += 1

    print(
        f"\n  {G}Kurulu:{RE} {W}{kurulu}{RE}  "
        f"{C}Repoda:{RE} {W}{repoda}{RE}  "
        f"{Y}Güncellenebilir:{RE} {W}{guncellenebilir}{RE}"
    )
    print(
        f"  {G}App:{RE} {class_counts.get('app',0)}  "
        f"{Y}Base:{RE} {class_counts.get('base',0)}  "
        f"{M}Tool:{RE} {class_counts.get('tool',0)}\n"
    )
    return 0


def nux_top():
    return size_report()


# ══════════════════════════════════════════════════════════════════
# GELİŞMİŞ KOMUTLAR
# ══════════════════════════════════════════════════════════════════

def mark_auto(pkgs):
    if root_kontrol():
        return 1
    db = db_yukle()
    for p in pkgs:
        if p not in db:
            uyari(f"Kurulu değil: {p}")
            continue
        db[p]["auto_installed"] = True
        db[p]["install_reason"] = "dependency"
        basari(f"Auto: {p}")
    db_kaydet(db)
    return 0


def mark_manual(pkgs):
    if root_kontrol():
        return 1
    db = db_yukle()
    for p in pkgs:
        if p not in db:
            uyari(f"Kurulu değil: {p}")
            continue
        db[p]["auto_installed"] = False
        db[p]["install_reason"] = "manual"
        basari(f"Manuel: {p}")
    db_kaydet(db)
    return 0


# ══════════════════════════════════════════════════════════════════
# SERVER / REPO BİLGİ
# ══════════════════════════════════════════════════════════════════

def mirror_test():
    from urllib.parse import urlparse
    host = urlparse(REPO_URL).hostname

    print(box_header("REPO BAĞLANTI TESTİ"))
    print(f"  Sunucu: {C}{host}{RE}\n")

    try:
        start = time.monotonic()
        socket.create_connection((host, 443), timeout=5)
        ms = (time.monotonic() - start) * 1000
        print(f"  {G}✓{RE} Bağlantı: {G}{ms:.0f} ms{RE}")
    except Exception as e:
        print(f"  {R}✗{RE} Bağlantı: {R}Başarısız ({e}){RE}")
        return 1

    try:
        start = time.monotonic()
        req = urllib.request.Request(REPO_INDEX)
        with urllib.request.urlopen(req, timeout=15) as response:
            data = response.read()
        sure = time.monotonic() - start
        boyut = len(data)
        hiz = (boyut / sure) / 1024 / 1024
        print(f"  {G}✓{RE} Index: {W}{boyut_formatla(boyut)}{RE}")
        print(f"  {G}✓{RE} Süre: {W}{sure:.2f}s{RE}")
        print(f"  {G}✓{RE} Hız: {G}{hiz:.2f} MB/s{RE}")
    except Exception as e:
        print(f"  {R}✗{RE} Test başarısız: {e}")

    print()
    return 0


def server_status():
    try:
        start = time.monotonic()
        req = urllib.request.Request(REPO_INDEX)
        with urllib.request.urlopen(req, timeout=10) as response:
            data = response.read()
            if response.headers.get("Content-Encoding") == "gzip":
                data = gzip.decompress(data)
        ms = (time.monotonic() - start) * 1000
        index = json.loads(data)
        online = True
    except Exception:
        online = False
        index = {}
        ms = 0

    pkgs = index.get("packages", {})
    db = db_yukle()

    print(box_header("NUX REPO SUNUCU DURUMU"))
    durum = f"{G}Çevrimiçi{RE}" if online else f"{R}Çevrimdışı{RE}"
    print(f"  {'Durum:':<22} {durum}")
    print(f"  {'Sunucu:':<22} {C}{REPO_URL}{RE}")
    if online:
        print(f"  {'Yanıt süresi:':<22} {W}{ms:.0f} ms{RE}")
    print(f"  {'Toplam paket:':<22} {W}{index.get('package_count', len(pkgs))}{RE}")
    print(f"  {'Yerel kurulu:':<22} {G}{len(db)} paket{RE}")
    print()
    return 0


def repo_info():
    index = repo_getir()
    if index is None:
        update()
        index = repo_getir()
    if index is None:
        return hata("Repo erişilemiyor.")

    pkgs = index.get("packages", {})
    class_counts = {"app": 0, "base": 0, "tool": 0}
    toplam_boyut = 0

    for name, pkg_data in pkgs.items():
        latest = pkg_data.get("latest")
        if not latest:
            continue
        info = pkg_data["versions"][latest]
        cls = info.get("class", paket_kategorisi(name, info))
        class_counts[cls] = class_counts.get(cls, 0) + 1
        toplam_boyut += int(info.get("size", 0) or 0)

    print(box_header("REPO BİLGİSİ"))
    print(f"  {'URL:':<22} {C}{REPO_URL}{RE}")
    print(f"  {'Toplam paket:':<22} {W}{len(pkgs)}{RE}")
    print(f"  {'Toplam boyut:':<22} {W}{boyut_formatla(toplam_boyut)}{RE}")
    for cls_name in ["app", "base", "tool"]:
        cnt = class_counts.get(cls_name, 0)
        if cnt > 0:
            print(f"  {CLASS_COLORS.get(cls_name, W)}{cls_name.upper():>6}:{RE} {W}{cnt}{RE}")
    print()
    return 0


def cache_info():
    cache = Path(CACHE_DIR)
    dosyalar = list(cache.rglob("*.nux")) + list(cache.rglob("*.partial"))
    toplam = sum(f.stat().st_size for f in dosyalar if f.exists())
    index_boyut = Path(INDEX_CACHE).stat().st_size if Path(INDEX_CACHE).exists() else 0

    print(box_header("CACHE BİLGİSİ"))
    print(f"  {'Cache dizini:':<25} {C}{CACHE_DIR}{RE}")
    print(f"  {'Paket cache:':<25} {W}{len(dosyalar)} dosya{RE}")
    print(f"  {'Paket cache boyutu:':<25} {W}{boyut_formatla(toplam)}{RE}")
    print(f"  {'Index cache:':<25} {W}{boyut_formatla(index_boyut)}{RE}")
    print(f"  {'Toplam:':<25} {Y}{boyut_formatla(toplam + index_boyut)}{RE}")
    if dosyalar:
        print(f"\n  {DIM}Temizlemek için: nux clean{RE}")
    print()
    return 0


def duplicates():
    index = repo_getir()
    if index is None:
        update()
        index = repo_getir()
    if index is None:
        return hata("Repo erişilemiyor.")

    pkgs = index.get("packages", {})
    bulundu = False

    print(box_header("ÇOKLU VERSİYON RAPORU"))

    for name, pkg_data in sorted(pkgs.items()):
        versions = list(pkg_data.get("versions", {}).keys())
        if len(versions) > 1:
            bulundu = True
            latest = pkg_data.get("latest", "?")
            versions_sorted = sorted(versions, key=cmp_to_key(versiyon_karsilastir), reverse=True)
            print(f"\n  {C}{name}{RE}")
            for v in versions_sorted:
                marker = f" {G}← güncel{RE}" if v == latest else f" {DIM}(eski){RE}"
                print(f"    {Y}{v:25}{RE}{marker}")

    if not bulundu:
        bilgi("Çoklu versiyonlu paket yok.")
    print()
    return 0


# ══════════════════════════════════════════════════════════════════
# HISTORY
# ══════════════════════════════════════════════════════════════════

def history_yaz(action, pkg, details=""):
    ensure_dirs()
    entry = {
        "timestamp": datetime.now().isoformat(),
        "action": action,
        "package": pkg,
        "details": details
    }
    try:
        history = []
        if Path(HISTORY_FILE).exists():
            with open(HISTORY_FILE) as f:
                history = json.load(f)
        history.append(entry)
        with open(HISTORY_FILE, "w") as f:
            json.dump(history, f, indent=2, ensure_ascii=False)
    except Exception:
        pass


def history_goster(limit=20):
    if not Path(HISTORY_FILE).exists():
        bilgi("Geçmiş yok.")
        return 0
    with open(HISTORY_FILE) as f:
        history = json.load(f)
    if not history:
        bilgi("Geçmiş yok.")
        return 0

    print(renkli(f"\nİşlem geçmişi (son {limit}):\n", W))
    for i, entry in enumerate(history[-limit:], len(history) - min(limit, len(history)) + 1):
        ts = entry.get("timestamp", "?")[:19]
        action = entry.get("action", "?")
        pkg = entry.get("package", "?")
        details = entry.get("details", "")
        renk_a = G if action == "INSTALL" else (R if action == "REMOVE" else C)
        detay = f" {DIM}({details}){RE}" if details else ""
        print(f"  {DIM}#{i:3}{RE} [{ts}] {renk_a}{action:12}{RE} {C}{pkg:25}{RE}{detay}")
    print(f"\n  {DIM}Geri almak için: nux rollback <#>{RE}\n")
    return 0


def rollback(target_id=None):
    if root_kontrol():
        return 1
    if not Path(HISTORY_FILE).exists():
        bilgi("Geçmiş yok.")
        return 0
    with open(HISTORY_FILE) as f:
        history = json.load(f)
    if not history:
        bilgi("Geçmiş yok.")
        return 0

    if target_id is not None:
        if target_id < 1 or target_id > len(history):
            return hata(f"Geçersiz ID. 1-{len(history)} arasında olmalı.")
        entry = history[target_id - 1]
    else:
        entry = history[-1]

    action = entry.get("action")
    pkg = entry.get("package")
    bilgi(f"Geri alınacak: {action} {pkg}")
    if not onayla("Devam edilsin mi?"):
        return 0

    if action == "INSTALL":
        result = remove([pkg])
    elif action == "REMOVE":
        result = install([pkg])
    else:
        uyari(f"{action} geri alma henüz desteklenmiyor.")
        return 1

    if result == 0:
        if target_id is None:
            history.pop()
            with open(HISTORY_FILE, "w") as f:
                json.dump(history, f, indent=2, ensure_ascii=False)
        basari("Geri alma tamamlandı.")
    return result


# ══════════════════════════════════════════════════════════════════
# YARDIM
# ══════════════════════════════════════════════════════════════════

def yardim():
    print(renkli(f"""
╔═══════════════════════════════════════════════════════════════╗
║                                                               ║
║         ███╗   ██╗██╗   ██╗██╗  ██╗                           ║
║         ████╗  ██║██║   ██║╚██╗██╔╝                           ║
║         ██╔██╗ ██║██║   ██║ ╚███╔╝                            ║
║         ██║╚██╗██║██║   ██║ ██╔██╗                            ║
║         ██║ ╚████║╚██████╔╝██╔╝ ██╗                           ║
║         ╚═╝  ╚═══╝ ╚═════╝ ╚═╝  ╚═╝                           ║
║                                                               ║
║    Nucora Linux Paket Yöneticisi  v{CLIENT_VERSION:<10}              ║
║    https://nucoralinux.com.tr                                 ║
║                                                               ║
╚═══════════════════════════════════════════════════════════════╝
""", C))

    print(renkli("▓▓ PAKET YÖNETİMİ", G))
    print(f"""
  {C}nux install{RE} <paket...>        Paket kur
  {C}nux remove{RE}  <paket...>        Paket kaldır
  {C}nux purge{RE}   <paket...>        Paket + ayarları sil
  {C}nux reinstall{RE} <paket...>      Yeniden kur
  {C}nux upgrade{RE}                   Güncelle (sadece app+tool)
  {C}nux update{RE}                    Repo listesini güncelle
  {C}nux local-install{RE} <dosya>     Yerel .nux kur
  {C}nux import{RE} <dosya>            Listeden toplu kur
  {C}nux export{RE}                    Paket listesi dışa aktar
  {C}nux dry-run{RE} <paket...>        Kurulum simülasyonu
""")

    print(renkli("▓▓ SORGULAMA", Y))
    print(f"""
  {C}nux search{RE}  <sorgu>           Paket ara
  {C}nux info{RE}    <paket>           Paket bilgisi + sınıf
  {C}nux list{RE}                      Kurulu paketler
  {C}nux list{RE} --upgradeable        Güncellenebilir
  {C}nux list-all{RE}                  Repo (app/base/tool ayrı)
  {C}nux depends{RE} <paket>           Bağımlılıklar
  {C}nux rdepends{RE} <paket>          Ters bağımlılıklar
  {C}nux why{RE} <paket>               Neden kurulu?
  {C}nux tree{RE} <paket>              Dependency ağacı
  {C}nux policy{RE}  <paket>           Versiyon politikası
  {C}nux count{RE}                     Hızlı özet
  {C}nux top{RE} / {C}nux size{RE}             Disk kullanım raporu
""")

    print(renkli("▓▓ BAKIM", R))
    print(f"""
  {C}nux verify{RE}  [paket...]        Dosya bütünlüğü
  {C}nux repair{RE}                    Bozuk paketleri onar
  {C}nux autoremove{RE}                Gereksiz paketleri sil
  {C}nux clean{RE}                     Cache temizle
  {C}nux cache-info{RE}                Cache boyut bilgisi
  {C}nux doctor{RE}                    Sistem sağlık kontrolü
  {C}nux db-clean{RE}                  Bozuk DB kayıtlarını temizle
  {C}nux duplicates{RE}                Çoklu versiyon raporu
""")

    print(renkli("▓▓ GELİŞMİŞ", M))
    print(f"""
  {C}nux pin{RE}     <paket...>        Sabitle
  {C}nux unpin{RE}   <paket...>        Sabitlemeyi kaldır
  {C}nux mark-auto{RE} <paket...>      Auto olarak işaretle
  {C}nux mark-manual{RE} <paket...>    Manuel olarak işaretle
  {C}nux history{RE}                   İşlem geçmişi
  {C}nux rollback{RE} [id]             Geri al
  {C}nux snapshot create{RE} [isim]    Snapshot oluştur
  {C}nux snapshot list{RE}             Snapshot listele
  {C}nux snapshot restore{RE} <isim>   Snapshot geri yükle
  {C}nux server-status{RE}             Repo durumu
  {C}nux repo-info{RE}                 Repo detay
  {C}nux mirror-test{RE}               Bağlantı testi
""")

    print(renkli("▓▓ SEÇENEKLER", C))
    print(f"""
  {Y}-y{RE}, {Y}--yes{RE}                 Tüm onayları kabul et
  {Y}-q{RE}, {Y}--quiet{RE}               Sessiz mod
  {Y}-qq{RE}                         Tam sessiz
  {Y}-v{RE}, {Y}--verbose{RE}             Ayrıntılı çıktı
""")

    print(renkli("▓▓ PAKET SINIFLARI", W))
    print(f"""
  {G}[APP]{RE}   Son kullanıcı uygulaması — upgrade'de güncellenir
  {Y}[BASE]{RE}  Sistem bağımlılığı — upgrade'de gizli, dependency olarak kurulur
  {M}[TOOL]{RE}  Nucora sistem aracı — korumalı, upgrade'de güncellenir
""")

    print(renkli("▓▓ DESTEK", M))
    print(f"""
  {Y}Forum:{RE}         {C}https://forum.nucoralinux.com.tr{RE}
  {Y}Dokümantasyon:{RE} {C}https://nucoralinux.com.tr/docs.html{RE}
  {Y}GitHub:{RE}        {C}https://github.com/AltunDev/nux{RE}
  {Y}Web:{RE}           {C}https://nucoralinux.com.tr{RE}
""")
    print(renkli("▓" * 64, C))
    print()


# ══════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════

def main():
    global QUIET, ASSUME_YES, VERBOSE

    args = sys.argv[1:]

    if "-qq" in args:
        QUIET = True
        args.remove("-qq")
    elif "-q" in args or "--quiet" in args:
        QUIET = True
        args = [a for a in args if a not in ("-q", "--quiet")]
    if "-y" in args or "--yes" in args:
        ASSUME_YES = True
        args = [a for a in args if a not in ("-y", "--yes")]
    if "-v" in args or "--verbose" in args:
        VERBOSE = True
        args = [a for a in args if a not in ("-v", "--verbose")]

    if not args:
        yardim()
        return 0

    cmd = args[0]
    params = args[1:]

    komutlar = {
        "install":        lambda: install(params),
        "remove":         lambda: remove(params),
        "purge":          lambda: remove(params, purge=True),
        "reinstall":      lambda: reinstall(params),
        "upgrade":        lambda: upgrade(),
        "update":         lambda: update(),
        "dry-run":        lambda: install(params, dry_run=True),
        "local-install":  lambda: local_install(params[0]) if params else hata("Dosya yolu belirtmelisiniz."),
        "import":         lambda: import_list(params[0]) if params else hata("Dosya yolu belirtmelisiniz."),
        "export":         lambda: export_packages(),
        "search":         lambda: search(params[0]) if params else hata("Arama terimi belirtmelisiniz."),
        "info":           lambda: info(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "show":           lambda: info(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "list":           lambda: listele("--upgradeable" in params or "-u" in params),
        "depends":        lambda: depends_cmd(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "rdepends":       lambda: depends_cmd(params[0], reverse=True) if params else hata("Paket adı belirtmelisiniz."),
        "why":            lambda: why(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "tree":           lambda: tree(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "list-all":       lambda: list_all(),
        "policy":         lambda: policy(params[0]) if params else hata("Paket adı belirtmelisiniz."),
        "verify":         lambda: verify(params or None),
        "repair":         lambda: repair(),
        "autoremove":     lambda: autoremove(),
        "clean":          lambda: clean(),
        "autoclean":      lambda: clean(),
        "cache-info":     lambda: cache_info(),
        "duplicates":     lambda: duplicates(),
        "repo-info":      lambda: repo_info(),
        "server-status":  lambda: server_status(),
        "mirror-test":    lambda: mirror_test(),
        "top":            lambda: nux_top(),
        "size":           lambda: size_report(),
        "count":          lambda: count(),
        "doctor":         lambda: doctor(),
        "db-clean":       lambda: db_clean(),
        "mark-auto":      lambda: mark_auto(params) if params else hata("Paket adı belirtmelisiniz."),
        "mark-manual":    lambda: mark_manual(params) if params else hata("Paket adı belirtmelisiniz."),
        "pin":            lambda: pin_ekle(params) if params else hata("Paket adı belirtmelisiniz."),
        "unpin":          lambda: pin_kaldir(params) if params else hata("Paket adı belirtmelisiniz."),
        "hold":           lambda: pin_ekle(params) if params else hata("Paket adı belirtmelisiniz."),
        "unhold":         lambda: pin_kaldir(params) if params else hata("Paket adı belirtmelisiniz."),
        "history":        lambda: history_goster(),
        "rollback":       lambda: (
            rollback(int(params[0])) if params and params[0].isdigit()
            else rollback()
        ),
        "snapshot":       lambda: (
            snapshot_create(params[1] if len(params) > 1 else None) if params and params[0] == "create"
            else snapshot_list() if params and params[0] == "list"
            else snapshot_restore(params[1]) if params and params[0] == "restore" and len(params) > 1
            else hata("Kullanım: nux snapshot create|list|restore <isim>")
        ),
        "log":            lambda: history_goster(),
        "help":           lambda: yardim(),
        "--help":         lambda: yardim(),
        "-h":             lambda: yardim(),
        "version":        lambda: print(renkli(f"nux version {CLIENT_VERSION} (Nucora Linux)", G)),
        "--version":      lambda: print(renkli(f"nux version {CLIENT_VERSION} (Nucora Linux)", G)),
    }

    if cmd in komutlar:
        try:
            return komutlar[cmd]() or 0
        except KeyboardInterrupt:
            print(renkli("\n[!] İşlem iptal edildi.", Y))
            return 130
        except Exception as e:
            if VERBOSE:
                import traceback
                print(traceback.format_exc())
            return hata(f"Beklenmeyen hata: {e}")

    hata(f"Bilinmeyen komut: {cmd}")
    print(renkli("\n  'nux help' ile yardıma bakabilirsiniz.\n", Y))
    return 1


if __name__ == "__main__":
    sys.exit(main())
