#!/usr/bin/env python3
"""gearS3.py — Browse, download and install Gear S3 apps & watch faces."""

import json
import os
import platform
import shutil
import stat
import subprocess
import sys
import ssl
import tempfile
import textwrap
import time
import urllib.request
import urllib.error
from pathlib import Path

STORE_URL = "https://gearS3.jnk.ovh"
API_URL = f"{STORE_URL}/api.php"
SDB_URLS = {
    "Linux": f"{STORE_URL}/sdb/linux/sdb",
    "Windows": f"{STORE_URL}/sdb/win/sdb.exe",
}
COLORS = not platform.system() == "Windows"


def c(s, code):
    return f"\033[{code}m{s}\033[0m" if COLORS else s


red = lambda s: c(s, "91")
green = lambda s: c(s, "92")
yellow = lambda s: c(s, "93")
blue = lambda s: c(s, "94")
bold = lambda s: c(s, "1")
dim = lambda s: c(s, "2")


def url_open(url, timeout=15):
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    req = urllib.request.Request(url, headers={"User-Agent": "gearS3.py/1.0"})
    return urllib.request.urlopen(req, timeout=timeout, context=ctx)


def fetch_json(url):
    try:
        resp = url_open(url)
        return json.loads(resp.read().decode())
    except urllib.error.URLError as e:
        print(red(f" Erreur reseau : {e.reason} "))
        print(yellow(f" Verifiez votre connexion internet "))
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(red(f" Erreur reponse serveur: {e} "))
        sys.exit(1)
    except Exception as e:
        print(red(f" Erreur inattendue : {e} "))
        sys.exit(1)


def download_file(url, dest, desc=""):
    try:
        with url_open(url, timeout=120) as r:
            total = int(r.headers.get("Content-Length", 0))
            written = 0
            with open(dest, "wb") as f:
                while True:
                    chunk = r.read(65536)
                    if not chunk:
                        break
                    f.write(chunk)
                    written += len(chunk)
                    if total:
                        pct = int(written * 100 / total)
                        if pct % 25 == 0 and pct > 0:
                            print(f"\r  {desc}: {pct}% ({written//1048576} MB / {total//1048576} MB)", end="", flush=True)
            print(f"\r  {desc}: 100% ({total//1048576} MB)" if total else f"\r  {desc}: OK ")
            return True
    except urllib.error.URLError as e:
        print(red(f"\n  Erreur telechargement {desc} : {e.reason} "))
        return False
    except Exception as e:
        print(red(f"\n  Erreur telechargement {desc} : {e} "))
        return False


def find_sdb():
    for name in ["sdb", "sdb.exe"]:
        p = shutil.which(name)
        if p:
            return p
        local = Path.cwd() / name
        if local.exists():
            return str(local)
    return None


def ensure_sdb():
    sdb = find_sdb()
    if sdb:
        print(green(f" SDB trouve : {sdb} "))
        return sdb
    system = platform.system()
    print(yellow(f" SDB introuvable. Telechargement... "))
    url = SDB_URLS.get(system)
    if url:
        ext = ".exe" if system == "Windows" else ""
        fname = f"sdb{ext}"
        dest = Path.cwd() / fname
        print(yellow(f" Telechargement de SDB pour {system}... "))
        if download_file(url, str(dest), "SDB"):
            if system != "Windows":
                dest.chmod(dest.stat().st_mode | stat.S_IEXEC)
            print(green(f" SDB pret : {dest} "))
            return str(dest)
        print(red(" ECHEC du telechargement de SDB "))
        print(yellow(f" Telechargez-le depuis {STORE_URL}/sdb/ et placez sdb{ext} dans le dossier du script "))
        sys.exit(1)
    else:
        sdb_url = f"{STORE_URL}/sdb/"
        print(red(f" {system} non supporte pour l'auto-download "))
        print(yellow(f" Obtenez SDB depuis le store : {sdb_url} "))
        print(yellow(" Ou placez sdb.exe dans le dossier du script "))
        sys.exit(1)


def sdb_stream(sdb, args, timeout=120, label=""):
    try:
        p = subprocess.Popen(
            [sdb] + args,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True, bufsize=1
        )
        last = ""
        while True:
            line = p.stdout.readline()
            if not line:
                break
            line = line.rstrip()
            if not line:
                continue
            last = line
            display = line[:72]
            if label:
                print(f"\r  {label}: {display}", end="", flush=True)
            else:
                print(f"  {display}")
        p.wait(timeout=timeout)
        if label:
            print(f"\r  {label}: termine" + " " * 40)
        ok = p.returncode == 0 or "val[ok]" in last or "val[ok]" in str(p.stdout)
        return ok, last
    except subprocess.TimeoutExpired:
        p.kill()
        if label:
            print(f"\r  {label}: TIMEOUT" + " " * 40)
        return False, "timeout"
    except FileNotFoundError:
        print(red(f"\n  SDB introuvable "))
        return False, "sdb introuvable"
    except Exception as e:
        if label:
            print(f"\r  {label}: ERREUR {e}" + " " * 40)
        return False, str(e)


SDB_PORT = 26101


def connect_watch(sdb, ip):
    if ":" not in ip:
        ip = f"{ip}:{SDB_PORT}"
    print(f" Connexion a {ip}... ", end="", flush=True)
    ok, out = sdb_stream(sdb, ["connect", ip], timeout=10)
    if ok and "connected" in out.lower():
        print(green("OK"))
        return True
    if "already connected" in out.lower():
        print(green("deja connecte"))
        return True
    print(red(f"ECHEC"))
    if out:
        print(dim(f" {out}"))
    return False


def install_wgt(sdb, wgt_path):
    tmp = "/opt/usr/home/owner/apps_rw/tmp/"
    fname = os.path.basename(wgt_path)
    remote = tmp + fname
    ok1, out1 = sdb_stream(sdb, ["push", wgt_path, remote], timeout=60, label="Push")
    if not ok1:
        return False, f"push echoue"
    ok2, out2 = sdb_stream(sdb, ["shell", "pkgcmd", "-w", "-i", "-t", "wgt", "-p", remote], timeout=120, label="Install")
    if not ok2 and "val[ok]" not in out2:
        return False, f"install echoue"
    return True, "OK"


def clear_screen():
    os.system("cls" if os.name == "nt" else "clear")


def show_items(items, page=0, search="", filter_type=""):
    per_page = 20
    filtered = items
    if filter_type:
        filtered = [i for i in items if i["type"] == filter_type]
    if search:
        s = search.lower()
        filtered = [i for i in filtered if s in i["name"].lower() or s in i["desc"].lower()]
    total = len(filtered)
    start = page * per_page
    end = min(start + per_page, total)
    page_items = filtered[start:end]
    return filtered, page_items, total, start, end


def display_page(page_items, start, end, total, page, search, filter_type):
    clear_screen()
    header = f" Gear S3 Store "
    print(bold(blue(header)))
    print("─" * 60)
    apps = sum(1 for i in page_items if i["type"] == "app")
    wfs = len(page_items) - apps
    label = {"app": "Samsung App", "watchface": "Watch Face"}
    for idx, item in enumerate(page_items):
        num = idx + 1
        icon = "📱" if item["type"] == "app" else "⌚"
        size_mb = item["size"] / 1048576
        print(f" {bold(str(num))}. {icon} {bold(item['name'])}")
        print(f"    {dim(label[item['type']])}  {dim(f'{size_mb:.1f} MB')}")
        desc = textwrap.shorten(item["desc"], width=70, placeholder="...")
        print(f"    {dim(desc)}")
        print()
    info = f"Page {page+1}  Items {start+1}–{end}/{total}"
    if search:
        info += f"  Recherche: '{search}'"
    if filter_type:
        info += f"  [{filter_type}]"
    print(dim(info))
    print("─" * 60)


def install_item(sdb, item, tmp_dir):
    name = item["name"]
    url = item["url"]
    fname = os.path.basename(url)
    dest = tmp_dir / fname
    print(f" [{green('>')}] {bold(name)}")
    if not download_file(url, str(dest), name):
        return False
    ok, msg = install_wgt(sdb, str(dest))
    if ok:
        print(green(f"  -> INSTALLE\n"))
        return True
    else:
        print(red(f"  -> ECHEC\n"))
        return False


def main():
    sdb = ensure_sdb()
    print(blue(" Chargement du catalogue..."))
    data = fetch_json(API_URL)
    items = data["items"]
    total = data["total"]
    apps_count = sum(1 for i in items if i["type"] == "app")
    wf_count = total - apps_count
    print(green(f" {total} items charges ({apps_count} apps, {wf_count} watch faces) "))
    time.sleep(0.5)

    page = 0
    search = ""
    filter_type = ""
    watch_ip = ""

    while True:
        filtered, page_items, ftotal, start, end = show_items(items, page, search, filter_type)
        display_page(page_items, start, end, ftotal, page, search, filter_type)
        if watch_ip:
            print(dim(f" Montre : {watch_ip}"))
        print(" [N] suivant  [P] precedent  [/] chercher")
        print(" [A] apps  [W] watch faces  [T] tout")
        print(" [1-{}] installer  [Q] quitter".format(len(page_items)))
        choice = input(" > ").strip().lower()
        if choice == "q":
            print(" Au revoir ")
            break
        elif choice == "n":
            if end < ftotal:
                page += 1
        elif choice == "p":
            if page > 0:
                page -= 1
        elif choice == "/":
            search = input(" Chercher : ").strip()
            page = 0
        elif choice == "a":
            filter_type = "app"
            page = 0
        elif choice == "w":
            filter_type = "watchface"
            page = 0
        elif choice == "t":
            filter_type = ""
            page = 0
        else:
            try:
                num = int(choice)
                if 1 <= num <= len(page_items):
                    item = page_items[num - 1]
                    if not watch_ip:
                        watch_ip = input(" IP de la montre (ex: 192.168.1.185) : ").strip()
                        if not watch_ip:
                            print(red(" IP requise "))
                            continue
                    if not connect_watch(sdb, watch_ip):
                        print(red(" Connexion impossible "))
                        retry = input(" Reessayer ? (o/N) : ").strip().lower()
                        if retry == "o" and not connect_watch(sdb, watch_ip):
                            print(red(" Abandon "))
                            continue
                        elif retry != "o":
                            continue
                    tmp_dir = Path(tempfile.mkdtemp(prefix="gearS3_"))
                    ok = install_item(sdb, item, tmp_dir)
                    shutil.rmtree(tmp_dir, ignore_errors=True)
                    input(dim(" Appuyez sur Entree pour continuer... "))
                    page = 0
                else:
                    print(red(" Numero invalide "))
                    time.sleep(0.5)
            except ValueError:
                print(red(" Commande invalide "))
                time.sleep(0.5)


if __name__ == "__main__":
    if "--help" in sys.argv or "-h" in sys.argv:
        print("gearS3.py — Outil CLI pour le Gear S3 Store")
        print()
        print("Usage:")
        print("  python3 gearS3.py              Lance l'interface interactive")
        print("  python3 gearS3.py --check      Verifie la connexion au store")
        print("  python3 gearS3.py --help       Affiche cette aide")
        print()
        print("Sites:")
        print(f"  Store  : {STORE_URL}")
        print(f"  API    : {API_URL}")
        sys.exit(0)
    if "--check" in sys.argv:
        print(blue(" Verification de la connexion..."))
        print(f" Store  : {STORE_URL}")
        try:
            url_open(STORE_URL)
            print(green(" Connexion store : OK "))
        except Exception as e:
            print(red(f" Connexion store : ECHEC - {e} "))
        print(f" API    : {API_URL}")
        try:
            d = fetch_json(API_URL)
            print(green(f" API : OK ({d['total']} items) "))
        except Exception as e:
            print(red(f" API : ECHEC - {e} "))
        sdb = find_sdb()
        if sdb:
            print(green(f" SDB : trouve ({sdb}) "))
        else:
            print(yellow(" SDB : non trouve (sera telecharge automatiquement) "))
        print(blue(" Verification terminee "))
        sys.exit(0)
    try:
        main()
    except KeyboardInterrupt:
        print("\n Au revoir ")
        sys.exit(0)
