#!/usr/bin/env python3
"""
╔══════════════════════════════════════════════════════════════╗
║   CGHEVEN Auto-Update Module  —  cgheven_auto_update.py      ║
║   Version: 1.0.0                                             ║
║                                                              ║
║   DaVinci Resolve ke liye — Blender wala auto_update.py      ║
║   nahi hai yeh. Is file mein koi bpy import nahi.            ║
║                                                              ║
║   Ye file CGHEVEN_Asset_Library.py ke SAATH WALE             ║
║   FOLDER mein honi chahiye:                                  ║
║                                                              ║
║   Windows:                                                   ║
║   C:/ProgramData/Blackmagic Design/DaVinci Resolve/          ║
║   Fusion/Scripts/Utility/                                    ║
╚══════════════════════════════════════════════════════════════╝

Backend Contract (Strapi):
  POST {BASE}/api/addon-updates/check
  Header: Content-Type: application/json
  Header: Authorization: Bearer <session_token>   (preferred)
  Body:   { "addon_slug": "DaVinci-Resolve-Asset-Library", "current_version": "1.0.0" }

  Response: { update_available, latest_version, changelog, download_url }

  POST {BASE}/api/addon-updates/download
  Header: Authorization: Bearer <session_token>
  Body:   { "addon_slug": "DaVinci-Resolve-Asset-Library" }
  Response: { download_url, version }
"""

from __future__ import annotations

import json
import os
import shutil
import sys
import tempfile
import threading
import urllib.error
import urllib.request
import zipfile
from typing import Callable, Optional

# ══════════════════════════════════════════════════════════════
#  CONFIG — Yeh 2 cheezein change karo har naye version pe
# ══════════════════════════════════════════════════════════════

ADDON_SLUG      = "DaVinci-Resolve-Asset-Library"   # Strapi Addon Release mein jo slug hai
CURRENT_VERSION = "1.0.0"            # Is file ka current version

# Ye cgheven_auth.py se import hoga — same server URL
# Agar import fail ho to fallback URL use hoga
try:
    from cgheven_auth import LICENSE_SERVER, get_machine_fingerprint
    _AUTH_AVAILABLE = True
except ImportError:
    LICENSE_SERVER      = "https://api.cgheven.com"
    _AUTH_AVAILABLE     = False
    def get_machine_fingerprint() -> str:  # type: ignore[misc]
        import hashlib, socket, platform
        try:    hostname = socket.gethostname()
        except: hostname = "unknown"
        user = os.getenv("USERNAME") or os.getenv("USER") or "unknown"
        return hashlib.sha256(f"{hostname}|{user}|{platform.system()}".encode()).hexdigest()

DEFAULT_TIMEOUT_CHECK    = 12
DEFAULT_TIMEOUT_DOWNLOAD = 120

# ══════════════════════════════════════════════════════════════
#  INSTALL PATH HELPERS
#  DaVinci Resolve pe addon ek .py file hai, folder nahi
# ══════════════════════════════════════════════════════════════

def _get_addon_file_path() -> str:
    """
    Is module ke saath wali CGHEVEN_Asset_Library.py ka full path.
    ZIP extract ke baad hum isi file ko replace karte hain.
    """
    return os.path.join(
        os.path.dirname(os.path.abspath(__file__)),
        "CGHEVEN_Asset_Library.py"
    )


def _get_cache_dir() -> str:
    """
    Temporary files ke liye folder.
    ZIP download aur extract yahan hota hai.
    """
    cache = os.path.join(tempfile.gettempdir(), "cgheven_update_cache")
    os.makedirs(cache, exist_ok=True)
    return cache


# ══════════════════════════════════════════════════════════════
#  CUSTOM EXCEPTION
# ══════════════════════════════════════════════════════════════

class UpdateApiError(Exception):
    def __init__(self, code: int, message: str):
        super().__init__(f"[{code}] {message}")
        self.code    = code
        self.message = message


# ══════════════════════════════════════════════════════════════
#  UPDATE STATE
#  Ek global object — poore module ka status yahan store hai
# ══════════════════════════════════════════════════════════════

class UpdateState:
    """
    Module ka live status.
    Main window is state ko read karke UI update karta hai.
    """
    __slots__ = (
        "update_available",   # bool  — naya version milaa?
        "update_version",     # str   — latest version string
        "update_url",         # str   — ZIP download URL
        "update_changelog",   # str   — kya naya aaya
        "update_checking",    # bool  — abhi check chal raha hai?
        "update_popup_shown", # bool  — popup ek baar dikhaya?
        "update_installing",  # bool  — install chal raha hai?
        "update_installed",   # bool  — install ho gaya?
        "update_status",      # str   — user-friendly status message
    )

    def __init__(self) -> None:
        self.update_available  = False
        self.update_version    = ""
        self.update_url        = ""
        self.update_changelog  = ""
        self.update_checking   = False
        self.update_popup_shown = False
        self.update_installing = False
        self.update_installed  = False
        self.update_status     = ""


# Module-level singleton — kahin se bhi import karke access karo
state = UpdateState()


# ══════════════════════════════════════════════════════════════
#  HTTP HELPER — JSON POST
# ══════════════════════════════════════════════════════════════

def _post_json(
    url: str,
    body: dict,
    *,
    token: Optional[str] = None,
    timeout: int         = DEFAULT_TIMEOUT_CHECK,
) -> dict:
    """
    JSON POST request karta hai.
    token mile to Authorization: Bearer header lagata hai.
    Server error ko UpdateApiError mein convert karta hai.
    """
    headers = {
        "Content-Type": "application/json",
        "Accept":       "application/json",
        "User-Agent":   "CGHEVEN-DaVinci/3.0",
    }
    if token and token.strip():
        headers["Authorization"] = f"Bearer {token.strip()}"

    data = json.dumps(body).encode("utf-8")
    req  = urllib.request.Request(url, data=data, headers=headers, method="POST")

    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            raw = resp.read().decode("utf-8")
            return json.loads(raw) if raw.strip() else {}

    except urllib.error.HTTPError as e:
        msg = e.reason or str(e.code)
        try:
            body_txt = e.read().decode("utf-8")
            if body_txt.strip():
                parsed = json.loads(body_txt)
                if isinstance(parsed, dict):
                    err = parsed.get("error")
                    if isinstance(err, dict):
                        msg = str(err.get("message") or err.get("name") or msg)
                    elif "message" in parsed:
                        msg = str(parsed.get("message") or msg)
        except Exception:
            pass
        raise UpdateApiError(int(e.code), msg) from e


# ══════════════════════════════════════════════════════════════
#  CORE: UPDATE CHECK
# ══════════════════════════════════════════════════════════════

def check_for_updates(
    *,
    session_token: str = "",
    license_key:   str = "",
    machine_hash:  str = "",
    base_url:      str = "",
    current_version: str = CURRENT_VERSION,
    addon_slug:    str = ADDON_SLUG,
) -> None:
    """
    Strapi ko POST /api/addon-updates/check bhejta hai.
    Result module-level ``state`` object mein store hota hai.

    Preferred:  session_token (cgheven_auth se mila hua)
    Fallback:   license_key + machine_hash seedha

    Ye blocking function hai — isko seedha call mat karo main thread se.
    ``run_check_threaded()`` use karo.
    """
    st = state
    if st.update_checking:
        return

    st.update_checking = True
    st.update_status   = ""

    tok   = (session_token or "").strip()
    key   = (license_key   or "").strip()
    mhash = (machine_hash  or "").strip()

    try:
        # Update check PUBLIC hai: bina token/license ke bhi chalega taaki free & logged-out
        # users ko bhi "update available" popup mile. Auth tabhi attach hoti hai jab present ho.
        base = (base_url or LICENSE_SERVER).rstrip("/")
        url  = f"{base}/api/addon-updates/check"

        body: dict = {
            "addon_slug":      (addon_slug or ADDON_SLUG).strip(),
            "current_version": (current_version or CURRENT_VERSION).strip(),
        }
        # Bearer token nahi hai par license_key + machine_hash hai to woh bhejo
        if not tok and key and mhash:
            body["license_key"]  = key
            body["machine_hash"] = mhash

        data = _post_json(url, body, token=tok or None, timeout=DEFAULT_TIMEOUT_CHECK)

        up       = data.get("update_available") is True
        latest   = str(data.get("latest_version") or "").strip()
        changelog= str(data.get("changelog")      or "").strip()
        dl       = str(data.get("download_url")   or "").strip()

        if not up:
            # Up to date
            st.update_available = False
            st.update_version   = ""
            st.update_changelog = ""
            st.update_url       = ""
            st.update_installed = False
            st.update_status    = f"v{current_version} — Up to date"
            return

        if not dl:
            # Update hai lekin URL nahi — edge case
            st.update_available = False
            st.update_version   = latest
            st.update_changelog = changelog
            st.update_url       = ""
            st.update_status    = "Update available lekin download URL nahi mili."
            return

        # Update available!
        st.update_available  = True
        st.update_version    = latest
        st.update_changelog  = changelog
        st.update_url        = dl
        st.update_popup_shown = False
        st.update_installed  = False
        st.update_status     = f"Update available: v{latest}"
        print(f"[UPDATE] New version available: v{latest}")

    except UpdateApiError as e:
        st.update_available = False
        st.update_version   = ""
        st.update_url       = ""
        st.update_changelog = ""
        st.update_installed = False
        if e.code == 401:
            st.update_status = "Session expire ho gaya — dobara activate karo."
        elif e.code == 403:
            st.update_status = "Ye license is product ke liye valid nahi."
        elif e.code == 400:
            st.update_status = f"Bad request: {e.message[:80]}"
        else:
            st.update_status = f"Update check fail ({e.code}): {e.message[:60]}"
        print(f"[UPDATE] Check error: {st.update_status}")

    except Exception as e:
        st.update_available = False
        st.update_status    = f"Update check fail: {str(e)[:80]}"
        print(f"[UPDATE] Unexpected error: {e}")

    finally:
        st.update_checking = False


# ══════════════════════════════════════════════════════════════
#  CORE: INSTALL UPDATE
#  ZIP download → extract → CGHEVEN_Asset_Library.py replace
# ══════════════════════════════════════════════════════════════

def install_update(
    *,
    on_progress: Optional[Callable[[str], None]] = None,
    on_complete: Optional[Callable[[], None]]    = None,
    on_error:    Optional[Callable[[str], None]] = None,
) -> None:
    """
    Background thread mein:
      1. state.update_url se ZIP download karo
      2. Extract karo
      3. CGHEVEN_Asset_Library.py dhoondo ZIP mein
      4. Current CGHEVEN_Asset_Library.py ko replace karo
      5. on_complete() call karo (ye tkinter dialog dikhata hai)

    Ye function immediately return karta hai — actual kaam
    background thread mein hota hai.
    """
    st = state
    if st.update_installing:
        return
    if not (st.update_url or "").strip():
        if on_error:
            on_error("Download URL nahi hai — pehle update check karo.")
        return

    st.update_installing = True
    st.update_installed  = False
    st.update_status     = "Downloading…"
    if on_progress:
        on_progress("Downloading…")

    def _worker() -> None:
        cache_dir   = _get_cache_dir()
        tmp_zip     = os.path.join(cache_dir, "cgheven_update.zip")
        tmp_extract = os.path.join(cache_dir, "cgheven_extracted")
        target_file = _get_addon_file_path()

        try:
            # ── Step 1: Download ZIP ────────────────────────────
            print(f"[UPDATE] Downloading from: {st.update_url}")
            req = urllib.request.Request(
                st.update_url,
                headers={"User-Agent": "CGHEVEN-DaVinci/3.0"}
            )
            with urllib.request.urlopen(req, timeout=DEFAULT_TIMEOUT_DOWNLOAD) as r:
                with open(tmp_zip, "wb") as f:
                    f.write(r.read())
            print(f"[UPDATE] ZIP downloaded: {tmp_zip}")

            st.update_status = "Installing…"
            if on_progress:
                on_progress("Installing…")

            # ── Step 2: Extract ZIP ─────────────────────────────
            if os.path.exists(tmp_extract):
                shutil.rmtree(tmp_extract, ignore_errors=True)
            os.makedirs(tmp_extract, exist_ok=True)

            with zipfile.ZipFile(tmp_zip, "r") as zf:
                zf.extractall(tmp_extract)
            print(f"[UPDATE] Extracted to: {tmp_extract}")

            # Zip cleanup
            try:
                os.remove(tmp_zip)
            except OSError:
                pass

            # ── Step 3: CGHEVEN_Asset_Library.py dhoondo ───────
            # ZIP ke andar CGHEVEN_Asset_Library.py honi chahiye
            main_py = None
            for root, _dirs, files in os.walk(tmp_extract):
                for fn in files:
                    if fn == "CGHEVEN_Asset_Library.py":
                        main_py = os.path.join(root, fn)
                        break
                if main_py:
                    break

            if not main_py:
                raise FileNotFoundError(
                    "CGHEVEN_Asset_Library.py ZIP mein nahi mili — "
                    "Strapi pe sahi ZIP upload karo."
                )

            # ── Step 4: Backup + Replace ────────────────────────
            backup = target_file + ".bak"
            if os.path.exists(target_file):
                shutil.copy2(target_file, backup)
                print(f"[UPDATE] Backup saved: {backup}")

            shutil.copy2(main_py, target_file)
            print(f"[UPDATE] Replaced: {target_file}")

            # Cleanup extract folder
            shutil.rmtree(tmp_extract, ignore_errors=True)

            # ── Step 5: State update ────────────────────────────
            new_ver = st.update_version
            st.update_status     = (
                f"Update v{new_ver} install ho gaya!\n"
                f"DaVinci Resolve restart karo changes apply karne ke liye."
            )
            st.update_available  = False
            st.update_installed  = True
            st.update_installing = False

            print(f"[UPDATE] Install complete — v{new_ver}")
            if on_complete:
                on_complete()

        except Exception as e:
            err_msg = f"Update fail ho gaya: {str(e)[:100]}"
            st.update_status     = err_msg
            st.update_installing = False
            print(f"[UPDATE] Install error: {e}")
            if on_error:
                on_error(err_msg)

    threading.Thread(target=_worker, daemon=True).start()


# ══════════════════════════════════════════════════════════════
#  BACKGROUND CHECK — Non-blocking
# ══════════════════════════════════════════════════════════════

def run_check_threaded(
    *,
    session_token:   str = "",
    license_key:     str = "",
    machine_hash:    str = "",
    base_url:        str = "",
    current_version: str = CURRENT_VERSION,
    addon_slug:      str = ADDON_SLUG,
    on_done: Optional[Callable[[], None]] = None,
) -> None:
    """
    Background thread mein update check karo — main window freeze nahi hoti.

    ``on_done`` callback main thread mein schedule karo (tkinter ``after``),
    wahan se ``show_update_notification()`` call karo agar
    ``state.update_available`` True ho.

    Example:
        run_check_threaded(
            session_token=sess_token,
            on_done=lambda: app.after(0, show_update_notification),
        )
    """
    def _work() -> None:
        check_for_updates(
            session_token   = session_token,
            license_key     = license_key,
            machine_hash    = machine_hash,
            base_url        = base_url,
            current_version = current_version,
            addon_slug      = (addon_slug or ADDON_SLUG).strip(),
        )
        if on_done:
            on_done()

    threading.Thread(target=_work, daemon=True).start()


# ══════════════════════════════════════════════════════════════
#  TKINTER UI HELPERS
#  CGHEVEN_Asset_Library.py mein call karo
# ══════════════════════════════════════════════════════════════

def show_update_notification(parent_window=None) -> None:
    """
    Agar update available hai to styled popup dikhao.

    - "Install Now"  → ZIP download + replace + restart message
    - "Skip"         → is session mein ek baar skip (agli baar DaVinci open hone pe fir dikhega)

    parent_window — CGHEVENApp instance ya koi bhi tkinter widget.
    Ye function SIRF main (tkinter) thread se call karo.

    Usage:
        import cgheven_auto_update as _upd
        _upd.run_check_threaded(
            session_token=sess_token,
            on_done=lambda: app.after(0, lambda: _upd.show_update_notification(app)),
        )
    """
    st = state

    # Update nahi hai ya is session mein already dikhaya
    if not st.update_available or st.update_popup_shown:
        return

    st.update_popup_shown = True  # Sirf is session ke liye — next launch pe reset hoga

    try:
        import tkinter as tk

        # ── Color palette — main app ke saath match karta hai ──────────
        BG      = "#1A1A2E"
        TOPBAR  = "#16213E"
        ACCENT  = "#FF8C00"
        GREEN   = "#4CAF50"
        TEXT    = "#E8E8F0"
        TEXT2   = "#888899"
        DIM     = "#555566"
        CARD    = "#252540"
        BORDER  = "#2E2E4E"
        GOLD    = "#FFD700"

        root = parent_window or tk._default_root

        popup = tk.Toplevel(root)
        popup.title("Update Available")
        popup.configure(bg=BG)
        popup.resizable(False, False)
        popup.transient(root)
        popup.grab_set()

        # Center the popup
        popup.update_idletasks()
        sw = popup.winfo_screenwidth()
        sh = popup.winfo_screenheight()
        popup.geometry(f"460x340+{sw//2-230}+{sh//2-170}")

        # ── Header ──────────────────────────────────────────────────────
        hdr = tk.Frame(popup, bg=TOPBAR, height=54)
        hdr.pack(fill="x")
        hdr.pack_propagate(False)

        tk.Label(hdr, text="  🔄  Update Available",
                 bg=TOPBAR, fg=ACCENT,
                 font=("Segoe UI", 13, "bold")).pack(side="left", padx=14, pady=14)
        tk.Label(hdr, text=f"v{CURRENT_VERSION}  →  v{st.update_version}",
                 bg=TOPBAR, fg=GOLD,
                 font=("Segoe UI", 9, "bold")).pack(side="right", padx=14)

        # ── Body ─────────────────────────────────────────────────────────
        body = tk.Frame(popup, bg=BG, pady=10)
        body.pack(fill="both", expand=True, padx=24)

        # Version row
        ver_frame = tk.Frame(body, bg=CARD,
                             highlightbackground=BORDER, highlightthickness=1)
        ver_frame.pack(fill="x", pady=(10, 6))
        tk.Label(ver_frame,
                 text=f"  Installed : v{CURRENT_VERSION}",
                 bg=CARD, fg=TEXT2,
                 font=("Segoe UI", 9), anchor="w").pack(fill="x", padx=12, pady=(8,2))
        tk.Label(ver_frame,
                 text=f"  New       : v{st.update_version}",
                 bg=CARD, fg=GREEN,
                 font=("Segoe UI", 10, "bold"), anchor="w").pack(fill="x", padx=12, pady=(2,8))

        # Changelog
        changelog = (st.update_changelog or "").strip()
        if changelog and changelog.lower() not in ("n/a", "none", "-"):
            tk.Label(body,
                     text="  What's new:",
                     bg=BG, fg=TEXT2,
                     font=("Segoe UI", 8, "bold"), anchor="w").pack(fill="x", pady=(4,2))
            cl_frame = tk.Frame(body, bg=CARD,
                                highlightbackground=BORDER, highlightthickness=1)
            cl_frame.pack(fill="x")
            tk.Label(cl_frame, text=f"  {changelog}",
                     bg=CARD, fg=TEXT,
                     font=("Segoe UI", 9),
                     wraplength=390, justify="left", anchor="w").pack(fill="x", padx=12, pady=8)

        tk.Label(body,
                 text="After install, restart DaVinci Resolve to apply changes.",
                 bg=BG, fg=DIM,
                 font=("Segoe UI", 8)).pack(pady=(10, 4))

        # ── Buttons ──────────────────────────────────────────────────────
        btn_frame = tk.Frame(body, bg=BG)
        btn_frame.pack(fill="x", pady=(6, 0))

        def _on_skip():
            popup.destroy()

        def _on_install():
            popup.destroy()
            _start_install_with_ui(parent_window)

        tk.Button(btn_frame,
                  text="Skip (remind next time)",
                  bg=CARD, fg=TEXT2,
                  relief="flat", cursor="hand2",
                  font=("Segoe UI", 9), padx=14, pady=7,
                  command=_on_skip).pack(side="left")

        tk.Button(btn_frame,
                  text="  Install Now  →",
                  bg=ACCENT, fg="white",
                  relief="flat", cursor="hand2",
                  font=("Segoe UI", 10, "bold"), padx=18, pady=7,
                  command=_on_install).pack(side="right")

    except Exception as e:
        print(f"[UPDATE] Notification dialog error: {e}")


def _start_install_with_ui(parent_window=None) -> None:
    """
    Install karo aur progress popup dikhao.
    Sirf show_update_notification se call hota hai.
    """
    try:
        import tkinter as tk
        from tkinter import messagebox

        BG     = "#1A1A2E"
        TOPBAR = "#16213E"
        ACCENT = "#FF8C00"
        TEXT   = "#E8E8F0"
        TEXT2  = "#888899"

        root = parent_window or tk._default_root

        # Progress popup
        prog = tk.Toplevel(root)
        prog.title("Installing Update…")
        prog.configure(bg=BG)
        prog.resizable(False, False)
        prog.transient(root)
        prog.grab_set()
        prog.update_idletasks()
        sw = prog.winfo_screenwidth()
        sh = prog.winfo_screenheight()
        prog.geometry(f"380x140+{sw//2-190}+{sh//2-70}")

        hdr = tk.Frame(prog, bg=TOPBAR, height=44)
        hdr.pack(fill="x"); hdr.pack_propagate(False)
        tk.Label(hdr, text="  🔄  Installing Update…",
                 bg=TOPBAR, fg=ACCENT,
                 font=("Segoe UI", 11, "bold")).pack(side="left", padx=12, pady=10)

        status_lbl = tk.Label(prog,
                              text="Connecting…",
                              bg=BG, fg=TEXT2,
                              font=("Segoe UI", 10))
        status_lbl.pack(expand=True)

        def _on_progress(msg: str) -> None:
            try:
                status_lbl.config(text=msg)
                prog.update_idletasks()
            except Exception:
                pass

        def _on_complete() -> None:
            try:
                prog.destroy()
            except Exception:
                pass
            # Styled final popup
            done_win = tk.Toplevel(root)
            done_win.title("Update Complete")
            done_win.configure(bg=BG)
            done_win.resizable(False, False)
            done_win.transient(root)
            done_win.grab_set()
            done_win.update_idletasks()
            sw2 = done_win.winfo_screenwidth()
            sh2 = done_win.winfo_screenheight()
            done_win.geometry(f"400x200+{sw2//2-200}+{sh2//2-100}")

            hdr2 = tk.Frame(done_win, bg=TOPBAR, height=44)
            hdr2.pack(fill="x"); hdr2.pack_propagate(False)
            tk.Label(hdr2, text="  ✅  Update Complete!",
                     bg=TOPBAR, fg="#4CAF50",
                     font=("Segoe UI", 11, "bold")).pack(side="left", padx=12, pady=10)

            body2 = tk.Frame(done_win, bg=BG)
            body2.pack(fill="both", expand=True, padx=24, pady=16)
            tk.Label(body2,
                     text=f"v{state.update_version} successfully installed.",
                     bg=BG, fg=TEXT,
                     font=("Segoe UI", 10, "bold")).pack(pady=(0, 6))
            tk.Label(body2,
                     text="Please restart DaVinci Resolve\nto apply the new version.",
                     bg=BG, fg=TEXT2,
                     font=("Segoe UI", 9)).pack()
            tk.Button(body2, text="  OK  ",
                      bg=ACCENT, fg="white",
                      relief="flat", cursor="hand2",
                      font=("Segoe UI", 9, "bold"), padx=14, pady=6,
                      command=done_win.destroy).pack(pady=(12, 0))

        def _on_error(msg: str) -> None:
            try:
                prog.destroy()
            except Exception:
                pass
            messagebox.showerror("Update Failed", msg, parent=root)

        def _done():
            if root:
                root.after(0, _on_complete)

        def _err(m: str):
            if root:
                root.after(0, lambda: _on_error(m))

        install_update(
            on_progress=_on_progress,
            on_complete=_done,
            on_error=_err,
        )

    except Exception as e:
        print(f"[UPDATE] Install UI error: {e}")


# ══════════════════════════════════════════════════════════════
#  QUICK TEST — directly run karo
#  python cgheven_auto_update.py
# ══════════════════════════════════════════════════════════════

if __name__ == "__main__":
    print("=" * 55)
    print("CGHEVEN Auto-Update Module — Quick Test")
    print("=" * 55)
    print(f"Addon Slug      : {ADDON_SLUG}")
    print(f"Current Version : {CURRENT_VERSION}")
    print(f"License Server  : {LICENSE_SERVER}")
    print(f"Auth Available  : {_AUTH_AVAILABLE}")
    print(f"Machine Hash    : {get_machine_fingerprint()[:16]}...")
    print(f"Addon File      : {_get_addon_file_path()}")
    print(f"Cache Dir       : {_get_cache_dir()}")
    print()
    print("Update state se check karne ke liye session token chahiye.")
    print("CGHEVEN_Asset_Library.py ke through test karo.")
