#!/usr/bin/env python3

import sys
import os
import json
import subprocess
import urllib.request
import threading

sys.path.insert(0, "/Libraries/Python/v3")
from ninf_parser import parse_ninf

import gi
gi.require_version("Notify", "0.7")
from gi.repository import Notify, GLib

CONFIG_FILE = os.path.expanduser("~/.config/NebiSoft/Bundle_Store/config.json")
APP_SOURCES_FILE = os.path.expanduser("~/.config/NebiSoft/Bundle_Store/appSources.json")
CSLB_VERSION_FILE = "/System/Configuration/CSLB_Version.txt"
BUNDLE_STORE_NAPP = "/Applications/Bundle Store.napp"


def load_config():
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, 'r') as f:
                return json.load(f)
        except:
            pass
    return {}


def load_app_sources():
    if os.path.exists(APP_SOURCES_FILE):
        try:
            with open(APP_SOURCES_FILE, 'r') as f:
                return json.load(f)
        except:
            pass
    return {}


def get_system_cslb_version():
    try:
        if os.path.exists(CSLB_VERSION_FILE):
            with open(CSLB_VERSION_FILE, 'r') as f:
                return f.read().strip()
    except:
        pass
    return None


def cslb_to_api_level(cslb_version):
    cslb_version = cslb_version.strip()
    if cslb_version.endswith('e'):
        return int(cslb_version[:-1]) + 1
    return int(cslb_version)


def find_best_napp_build(storefront, system_cslb):
    if not system_cslb:
        return None
    system_api_level = cslb_to_api_level(system_cslb)
    available_builds = []
    for key in storefront.keys():
        if key.startswith('app.LastCSLB') and 'Build' in key and '.Version' in key:
            parts = key.split('.')
            if len(parts) >= 2:
                cslb_part = parts[1].replace('LastCSLB', '').replace('Build', '')
                arch = parts[2] if len(parts) > 2 else "unknown"
                try:
                    build_api_level = cslb_to_api_level(cslb_part)
                    if system_api_level >= build_api_level:
                        version = storefront.get(f"app.LastCSLB{cslb_part}Build.{arch}.Version", "")
                        last_updated = storefront.get(f"app.LastCSLB{cslb_part}Build.{arch}.LastUpdated", "")
                        if version:
                            available_builds.append({
                                'api_level': build_api_level,
                                'version': version,
                                'last_updated': last_updated
                            })
                except:
                    continue
    if not available_builds:
        return None
    available_builds.sort(key=lambda x: x['api_level'], reverse=True)
    best = available_builds[0]
    return {'version': best['version'], 'last_updated': best['last_updated']}


def fetch_ninf(url):
    req = urllib.request.Request(url)
    req.add_header('User-Agent', 'Bundle Store/NebiOS')
    with urllib.request.urlopen(req, timeout=8) as resp:
        return resp.read().decode('utf-8')


def parse_storefront(content):
    parsed = parse_ninf(content)
    data = {}
    for spec in parsed.arg_specs:
        if spec.type == "array" and spec.choices:
            data[spec.name] = spec.choices
        else:
            data[spec.name] = spec.default
    return data


def get_os_version():
    try:
        with open("/etc/os-release", 'r') as f:
            for line in f:
                if line.startswith("VERSION="):
                    return line.strip().split("=", 1)[1].strip('"')
    except:
        pass
    return None


def check_os_update(config, system_cslb):
    """Returns (old_version, new_version) or None"""
    os_version = get_os_version()
    if not os_version:
        return None

    repo_url = "apps.nebios.org"
    try:
        stable_content = fetch_ninf(f"https://{repo_url}/apps/NebiOS_Update/storefront.ninf")
        stable_data = parse_storefront(stable_content)
        stable_build = find_best_napp_build(stable_data, system_cslb)
    except Exception as e:
        print(f"[OS Update] Error fetching stable: {e}")
        return None

    best_build = stable_build
    best_data = stable_data

    beta_enabled = config.get("betaOsUpdates", False)
    if beta_enabled:
        try:
            beta_content = fetch_ninf(f"https://{repo_url}/apps/NebiOS_Beta_Update/storefront.ninf")
            beta_data = parse_storefront(beta_content)
            beta_build = find_best_napp_build(beta_data, system_cslb)
            if beta_build and beta_build.get('version'):
                stable_lu = stable_build.get('last_updated', '') if stable_build else ''
                beta_lu = beta_build.get('last_updated', '')
                if beta_lu > stable_lu:
                    print(f"[OS Update] Beta is newer, using beta")
                    best_build = beta_build
                    best_data = beta_data
        except Exception as e:
            print(f"[OS Update] Error fetching beta: {e}")

    if not best_build or not best_build.get('version'):
        return None

    remote_version = best_build['version']
    if os_version != remote_version:
        return (os_version, remote_version, best_data is not stable_data)
    return None


def check_napp_updates(system_cslb):
    """Returns count of NAPP updates available"""
    count = 0
    app_sources = load_app_sources()
    for app_name, repo_url in app_sources.items():
        try:
            user_path = os.path.expanduser(f"~/Applications/{app_name}.napp")
            system_path = f"/Applications/{app_name}.napp"
            napp_path = user_path if os.path.exists(user_path) else (system_path if os.path.exists(system_path) else None)
            if not napp_path:
                continue

            result = subprocess.run(
                ["bash", napp_path, "--lsm"],
                capture_output=True, text=True, timeout=3
            )
            if result.returncode != 0:
                continue

            installed_version = None
            for spec in parse_ninf(result.stdout).arg_specs:
                if spec.name == "app.Version":
                    installed_version = spec.default
                    break
            if not installed_version:
                continue

            repo_url_full = f"https://{repo_url}" if not repo_url.startswith("http") else repo_url
            app_name_safe = app_name.replace(' ', '_')
            ninf_content = fetch_ninf(f"{repo_url_full}/apps/{app_name_safe}/storefront.ninf")
            app_data = parse_storefront(ninf_content)
            best_build = find_best_napp_build(app_data, system_cslb)
            if best_build and best_build.get('version') and installed_version != best_build['version']:
                count += 1
        except Exception as e:
            print(f"[NAPP Updates] Error checking {app_name}: {e}")
            continue
    return count


def check_flatpak_updates():
    """Returns count of Flatpak updates available using commit comparison"""
    count = 0
    try:
        env = os.environ.copy()
        env['LC_ALL'] = 'C'
        result = subprocess.run(
            ["flatpak", "list", "--app"],
            capture_output=True, text=True, timeout=10, env=env
        )
        if result.returncode != 0:
            return 0
        lines = result.stdout.strip().split('\n')
        for i, line in enumerate(lines):
            if not line.strip():
                continue
            if i == 0 and ('Name' in line or 'Application' in line or line.startswith('Ad\t')):
                continue
            parts = line.split('\t')
            if len(parts) < 2:
                continue
            app_id = parts[1].strip()
            installed_commit = None
            for scope in ["--user", "--system"]:
                info = subprocess.run(
                    ["flatpak", "info", scope, app_id],
                    capture_output=True, text=True, timeout=3, env=env
                )
                if info.returncode == 0:
                    for l in info.stdout.split('\n'):
                        if l.strip().startswith("Commit:"):
                            installed_commit = l.split(":", 1)[1].strip()
                    if installed_commit:
                        break
            if not installed_commit:
                continue
            remote_commit = None
            for scope in ["--user", "--system"]:
                remote = subprocess.run(
                    ["flatpak", "remote-info", scope, "flathub", app_id],
                    capture_output=True, text=True, timeout=3, env=env
                )
                if remote.returncode == 0:
                    for l in remote.stdout.split('\n'):
                        if l.strip().startswith("Commit:"):
                            remote_commit = l.split(":", 1)[1].strip()
                    if remote_commit:
                        break
            if remote_commit and installed_commit != remote_commit:
                count += 1
    except Exception as e:
        print(f"[Flatpak Updates] Error: {e}")
    return count


loop = None


def open_bundle_store(notification, action_key, user_data):
    subprocess.Popen(["napp-run", BUNDLE_STORE_NAPP, "--", "--updates"])
    GLib.idle_add(loop.quit)


def main():
    global loop
    Notify.init("bs-update-notifier")
    config = load_config()
    system_cslb = get_system_cslb_version()

    loop = GLib.MainLoop()
    notifications = []

    def show_notification(summary, body, icon):
        n = Notify.Notification.new(summary, body, icon)
        n.add_action("open-bs", "Open Bundle Store", open_bundle_store, None)
        n.show()
        notifications.append(n)

    def run_checks():
        found_anything = False

        os_result = check_os_update(config, system_cslb)
        if os_result:
            old_ver, new_ver, is_beta = os_result
            title = "NebiOS Beta Update Available" if is_beta else "NebiOS Update Available"
            GLib.idle_add(show_notification, title, f"{old_ver} \u2192 {new_ver}", "software-center")
            found_anything = True

        napp_count = check_napp_updates(system_cslb)
        flatpak_count = check_flatpak_updates()
        total = napp_count + flatpak_count
        if total > 0:
            title = f"Updates for {total} app{'s' if total != 1 else ''} available"
            parts = []
            if napp_count:
                parts.append(f"{napp_count} NAPP")
            if flatpak_count:
                parts.append(f"{flatpak_count} Flatpak")
            body = ", ".join(parts)
            GLib.idle_add(show_notification, title, body, "software-center")
            found_anything = True

        if not found_anything:
            GLib.idle_add(loop.quit)
        else:
            # Quit after 2 minutes if user doesn't click anything
            GLib.timeout_add_seconds(120, loop.quit)

    t = threading.Thread(target=run_checks, daemon=True)
    t.start()
    loop.run()


if __name__ == "__main__":
    main()
