#!/data/data/com.termux/files/usr/bin/env python3
"""
Purrfect CLI - Non-Root Auto Patcher
Automates fetching, patching, and bundling social media APKs (Reddit, Snapchat, Instagram, WhatsApp)
with the Purrfect Xposed module using LSPatch.
"""

import sys
import os
import argparse
import subprocess
import json
import zipfile
import shutil
import urllib.request
import urllib.error
import re
from pathlib import Path

# ANSI Colors
CYAN = "\033[96m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
BOLD = "\033[1m"
RESET = "\033[0m"

LSPATCH_JAR_URL = "https://github.com/JingMatrix/LSPatch/releases/download/v0.8/lspatch.jar"
PURRFECT_RELEASES_URL = "https://api.github.com/repos/piel-m/purrfect/releases/latest"

AUTO_PATCH_REPOS = {
    "reddit": [
        "https://api.github.com/repos/particle-box/download-reddit/releases/latest",
        "https://api.github.com/repos/curious-freak/download-reddit/releases/latest"
    ],
    "snapchat": [
        "https://www.purrfectgit.com/api/repos/particle-box/download-snap/releases/latest"
    ],
    "instagram": [
        "https://www.purrfectgit.com/api/repos/particle-box/download-insta/releases/latest"
    ]
}

def banner():
    print(f"{CYAN}{BOLD}")
    print("==========================================================")
    print("           🐾 PURRFECT CLI - NON-ROOT AUTO PATCHER 🐾     ")
    print("==========================================================")
    print(f"{RESET}")

def info(msg):
    print(f"{CYAN}[*]{RESET} {msg}")

def success(msg):
    print(f"{GREEN}[+]{RESET} {msg}")

def warn(msg):
    print(f"{YELLOW}[!]{RESET} {msg}")

def error(msg, fatal=True):
    print(f"{RED}[X] Error:{RESET} {msg}")
    if fatal:
        sys.exit(1)

def http_get_json(url):
    req = urllib.request.Request(url, headers={"User-Agent": "Purrfect-CLI/1.0"})
    try:
        with urllib.request.urlopen(req) as resp:
            data = resp.read().decode('utf-8')
            return json.loads(data)
    except Exception as e:
        return None

def download_file(url, output_path, desc="file"):
    info(f"Downloading {desc} from {url}...")
    req = urllib.request.Request(url, headers={"User-Agent": "Purrfect-CLI/1.0"})
    try:
        with urllib.request.urlopen(req) as resp, open(output_path, 'wb') as out_file:
            shutil.copyfileobj(resp, out_file)
        success(f"Downloaded {desc} -> {output_path}")
    except Exception as e:
        error(f"Failed to download {desc}: {e}")

def ensure_lspatch_jar(work_dir):
    jar_path = os.path.join(work_dir, "lspatch.jar")
    if not os.path.exists(jar_path):
        info("lspatch.jar not found. Fetching LSPatch CLI binary...")
        download_file(LSPATCH_JAR_URL, jar_path, "LSPatch CLI")
    return jar_path

def fetch_latest_purrfect_module(work_dir):
    module_path = os.path.join(work_dir, "purrfect-module.apk")
    if os.path.exists(module_path):
        return module_path

    info("Searching local workspace and downloads for Purrfect module APK...")

    # Search local directory or /sdcard/Download
    search_paths = [
        work_dir,
        os.path.expanduser("~/Projects/Purrfect"),
        "/sdcard/Download/APKs",
        "/sdcard/Download"
    ]
    for spath in search_paths:
        if os.path.exists(spath):
            for fname in os.listdir(spath):
                if fname.startswith("purrfect") and fname.endswith(".apk") and "patched" not in fname:
                    found = os.path.join(spath, fname)
                    info(f"Found Purrfect module APK: {found}")
                    return found

    info("Fetching latest Purrfect module from GitHub releases...")
    rel_json = http_get_json(PURRFECT_RELEASES_URL)
    if rel_json and "assets" in rel_json:
        for asset in rel_json["assets"]:
            if asset["name"].endswith(".apk"):
                download_file(asset["browser_download_url"], module_path, "Purrfect Module APK")
                return module_path

    error("Could not find or download Purrfect module APK. Please provide one with --module <path>")

def fetch_latest_target_apk(target_app, work_dir):
    if target_app not in AUTO_PATCH_REPOS:
        error(f"Auto-fetching not supported for target '{target_app}'. Please pass an APK file path.")

    info(f"Auto-fetching latest base release for target app '{target_app}'...")
    repos = AUTO_PATCH_REPOS[target_app]
    for repo_url in repos:
        data = http_get_json(repo_url)
        if data and "assets" in data:
            for asset in data["assets"]:
                aname = asset.get("name", "")
                if aname.endswith(".apk") or aname.endswith(".apkm") or aname.endswith(".apks"):
                    dl_url = asset.get("browser_download_url")
                    out_path = os.path.join(work_dir, f"target_{target_app}_{aname}")
                    download_file(dl_url, out_path, f"Target {target_app} APK")
                    return out_path
    error(f"Could not auto-fetch APK for {target_app}. Please provide a local APK file path.")

def extract_bundle(bundle_path, extract_dir):
    info(f"Extracting APK bundle archive: {bundle_path}...")
    os.makedirs(extract_dir, exist_ok=True)
    with zipfile.ZipFile(bundle_path, 'r') as zip_ref:
        zip_ref.extractall(extract_dir)

    apk_files = [os.path.join(extract_dir, f) for f in os.listdir(extract_dir) if f.endswith(".apk")]
    if not apk_files:
        error(f"No .apk files found inside bundle {bundle_path}")

    base_apk = next((f for f in apk_files if os.path.basename(f) == "base.apk"), apk_files[0])
    splits = [f for f in apk_files if f != base_apk]
    return base_apk, splits

def create_apks_bundle(splits_dir, output_apks_path):
    info(f"Packaging patched split APKs into APKS bundle: {output_apks_path}...")
    with zipfile.ZipFile(output_apks_path, "w", zipfile.ZIP_STORED) as zipf:
        for fname in os.listdir(splits_dir):
            if fname.endswith(".apk"):
                fpath = os.path.join(splits_dir, fname)
                zipf.write(fpath, fname)
    success(f"Created APKS bundle -> {output_apks_path}")

def run_patch(target_input, module_apk, output_dir):
    work_dir = os.path.abspath(os.getcwd())
    lspatch_jar = ensure_lspatch_jar(work_dir)

    if not module_apk:
        module_apk = fetch_latest_purrfect_module(work_dir)

    target_input = os.path.abspath(target_input)
    module_apk = os.path.abspath(module_apk)

    if not os.path.exists(target_input):
        error(f"Target input file or directory '{target_input}' does not exist.")

    if not os.path.exists(module_apk):
        error(f"Purrfect module APK '{module_apk}' does not exist.")

    os.makedirs(output_dir, exist_ok=True)
    output_dir = os.path.abspath(output_dir)

    # Determine input type (.apk, .apkm, .apks, or directory)
    input_apks = []
    temp_extract_dir = None

    if os.path.isdir(target_input):
        input_apks = [os.path.join(target_input, f) for f in os.listdir(target_input) if f.endswith(".apk")]
    elif target_input.endswith(".apkm") or target_input.endswith(".apks") or target_input.endswith(".xapk"):
        temp_extract_dir = os.path.join(work_dir, ".temp_bundle_extract")
        if os.path.exists(temp_extract_dir):
            shutil.rmtree(temp_extract_dir)
        base_apk, splits = extract_bundle(target_input, temp_extract_dir)
        input_apks = [base_apk] + splits
    elif target_input.endswith(".apk"):
        input_apks = [target_input]
    else:
        error(f"Unsupported file format: {target_input}. Expected .apk, .apkm, .apks, .xapk, or directory.")

    info(f"Target APK(s) to patch: {len(input_apks)} file(s)")
    info(f"Purrfect Module APK: {module_apk}")
    info("Executing LSPatch Engine...")

    cmd = [
        "java", "-jar", lspatch_jar
    ] + input_apks + [
        "-m", module_apk,
        "--embed",
        "--sigbypasslv", "2",
        "-f",
        "-o", output_dir
    ]

    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

    if result.returncode != 0 and "Done." not in result.stdout:
        print(result.stdout)
        error(f"LSPatch process failed with code {result.returncode}")

    patched_apks = [os.path.join(output_dir, f) for f in os.listdir(output_dir) if f.endswith(".apk")]

    if not patched_apks:
        error("No output APKs generated by LSPatch.")

    success("LSPatch process completed successfully!")
    info(f"Output files written to: {output_dir}")

    # If multiple split APKs were patched, package them into an .apks bundle as well
    if len(patched_apks) > 1:
        target_name = os.path.basename(target_input).replace(".", "_")
        apks_bundle_name = f"{target_name}_purrfect_patched.apks"
        apks_bundle_path = os.path.join(output_dir, apks_bundle_name)
        create_apks_bundle(output_dir, apks_bundle_path)

        # Copy to /sdcard/Download if available
        if os.path.exists("/sdcard/Download"):
            sd_dest = os.path.join("/sdcard/Download", apks_bundle_name)
            shutil.copy(apks_bundle_path, sd_dest)
            success(f"Copied APKS bundle to Download folder: {sd_dest}")
    else:
        patched_apk = patched_apks[0]
        if os.path.exists("/sdcard/Download"):
            sd_dest = os.path.join("/sdcard/Download", os.path.basename(patched_apk))
            shutil.copy(patched_apk, sd_dest)
            success(f"Copied patched APK to Download folder: {sd_dest}")

    if temp_extract_dir and os.path.exists(temp_extract_dir):
        shutil.rmtree(temp_extract_dir)

def main():
    banner()
    parser = argparse.ArgumentParser(
        description="Purrfect CLI - Non-Root Auto Patcher for Reddit, Snapchat, Instagram, WhatsApp",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    subparsers = parser.add_subparsers(dest="command", help="Available CLI commands")

    # Command: patch
    patch_parser = subparsers.add_parser("patch", help="Patch an APK or App Bundle with Purrfect")
    patch_parser.add_argument("target", nargs="?", help="Target APK file (.apk, .apkm, .apks) or target app name (reddit, snapchat, instagram)")
    patch_parser.add_argument("-m", "--module", help="Path to Purrfect Xposed module APK (auto-fetched if omitted)")
    patch_parser.add_argument("--auto-fetch", action="store_true", help="Auto-fetch the latest base APK release for specified target app")
    # Command: list-targets
    subparsers.add_parser("list-targets", help="List supported target apps and features")


    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(0)



    if args.command == "patch":
        target_val = getattr(args, "target", None)
        if not target_val:
            patch_parser.print_help()
            sys.exit(1)

        target = target_val.lower()
        work_dir = os.path.abspath(os.getcwd())

        if target in AUTO_PATCH_REPOS or getattr(args, "auto_fetch", False):
            if os.path.exists(target_val):
                target_apk = target_val
            else:
                target_apk = fetch_latest_target_apk(target, work_dir)
        else:
            target_apk = target_val

        run_patch(target_apk, getattr(args, "module", None), getattr(args, "output", "./output"))


    elif args.command == "list-targets":
        print(f"{BOLD}Supported Target Apps & Auto-Patch Capabilities:{RESET}\n")
        print("  1. Reddit    (com.reddit.frontpage) - Ad block, Premium UI, Link redirect, Screenshot popup bypass")
        print("  2. Snapchat  (com.snapchat.android)  - Privacy, Media Downloader, Unlimited Snap view, Screenshot bypass")
        print("  3. Instagram (com.instagram.android) - Ad block, Feed customization, Media downloader")
        print("  4. WhatsApp  (com.whatsapp)         - Privacy enhancements, Message retention")
        print("\nUse: purrfect-cli patch <target_apk_or_app_name> [-m module.apk] [-o output_dir]")

if __name__ == "__main__":
    main()
