import sys
import time
import subprocess
from PyQt6.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton,
    QVBoxLayout, QHBoxLayout, QListWidget,
    QFileDialog, QMessageBox, QTextEdit,
    QDialog, QLineEdit, QInputDialog
)
from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtCore import QSettings
import os
# ========================
# CONFIG
# ========================
ADB_PATH = r"C:\LDPlayer\LDPlayer9\adb.exe"
LDCONSOLE = r"C:\LDPlayer\LDPlayer9\ldconsole.exe"
PACKAGE = "jp.pokemon.pokemontcgp"
REMOTE_XML = f"/data/data/{PACKAGE}/shared_prefs/deviceAccount:.xml"
def get_app_uid(device, log_callback=None):
        r = run(
            [ADB_PATH, "-s", device, "shell", "dumpsys", "package", PACKAGE],
            log_callback=None
        )

        for line in r.stdout.splitlines():
            line = line.strip()
            if line.startswith("userId="):
                uid_num = int(line.split("=")[1])
                app_uid = f"u0_a{uid_num - 10000}"
                if log_callback:
                    log_callback.emit(f"▶ Detected app UID: {app_uid}")
                return app_uid

        raise RuntimeError("❌ Failed to detect app UID")
# ========================
# SETTINGS DIALOG
# ========================
class SettingsDialog(QDialog):
    def __init__(self, settings, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.setWindowTitle("Settings")
        self.setFixedSize(600, 160)

        self.adb_edit = QLineEdit(self.settings.value("adb_path", ADB_PATH))
        self.ld_edit = QLineEdit(self.settings.value("ldconsole_path", LDCONSOLE))

        adb_btn = QPushButton("Browse")
        ld_btn = QPushButton("Browse")
        save_btn = QPushButton("Save")

        adb_btn.clicked.connect(self.browse_adb)
        ld_btn.clicked.connect(self.browse_ld)
        save_btn.clicked.connect(self.save)

        layout = QVBoxLayout(self)

        r1 = QHBoxLayout()
        r1.addWidget(QLabel("ADB Path"))
        r1.addWidget(self.adb_edit)
        r1.addWidget(adb_btn)

        r2 = QHBoxLayout()
        r2.addWidget(QLabel("LDConsole Path"))
        r2.addWidget(self.ld_edit)
        r2.addWidget(ld_btn)

        layout.addLayout(r1)
        layout.addLayout(r2)
        layout.addWidget(save_btn)

    def browse_adb(self):
        path, _ = QFileDialog.getOpenFileName(self, "Select adb.exe", "", "adb.exe")
        if path:
            self.adb_edit.setText(path)

    def browse_ld(self):
        path, _ = QFileDialog.getOpenFileName(self, "Select ldconsole.exe", "", "ldconsole.exe")
        if path:
            self.ld_edit.setText(path)

    def save(self):
        self.settings.setValue("adb_path", self.adb_edit.text())
        self.settings.setValue("ldconsole_path", self.ld_edit.text())
        self.accept()

# ========================
# HELPERS
# ========================
def run(cmd, log_callback=None):
    try:
        creationflags = subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
        p = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            creationflags=creationflags
        )
    except FileNotFoundError:
        msg = f"❌ Executable not found:\n{cmd[0]}"
        if log_callback:
            log_callback.emit(msg)
        else:
            print(msg)
        return subprocess.CompletedProcess(cmd, 1, "", msg)

    msg = f"$ {' '.join(cmd)}"
    if p.stdout:
        msg += "\n" + p.stdout.strip()
    if p.stderr:
        msg += "\n[stderr] " + p.stderr.strip()

    if log_callback:
        log_callback.emit(msg)

    return p


def list_instances():
    if not os.path.exists(LDCONSOLE):
        return []

    r = run([LDCONSOLE, "list2"])

    if not r or not r.stdout:
        return []

    out = []
    for line in r.stdout.splitlines():
        p = line.split(",")
        if len(p) >= 2:
            out.append((p[0].strip(), p[1].strip()))
    return out


CREATE_NO_WINDOW = 0x08000000

def list_adb_devices(retry=5, wait=1):
    for _ in range(retry):
        r = subprocess.run(
            [ADB_PATH, "devices"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            creationflags=CREATE_NO_WINDOW
        )
        devs = [l.split("\t")[0] for l in r.stdout.splitlines() if "\tdevice" in l]
        if devs:
            return devs
        time.sleep(wait)
    return []

# ========================
# WORKER THREAD
# ========================
class AdbWorker(QThread):
    log_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(str)

    def __init__(self, func, *args):
        super().__init__()
        self.func = func
        self.args = args

    def run(self):
        try:
            result = self.func(*self.args, log_callback=self.log_signal)
            self.finished_signal.emit(result if result else "Done")
        except Exception as e:
            self.finished_signal.emit(f"오류 발생: {e}")

# ========================
# GUI
# ========================
class AccountTool(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PTCGP Account Tool")
        self.setFixedSize(900, 600)

        self.settings = QSettings("DevaretaStudio", "PTCGPAccountTool")
        global ADB_PATH, LDCONSOLE
        ADB_PATH = self.settings.value("adb_path", ADB_PATH)
        LDCONSOLE = self.settings.value("ldconsole_path", LDCONSOLE)

        self.layout = QVBoxLayout(self)

        self.settings_btn = QPushButton("⚙ Settings")
        self.settings_btn.clicked.connect(self.open_settings)
        self.layout.addWidget(self.settings_btn)

        self.instance_list = QListWidget()
        self.device_list = QListWidget()

        self.log_box = QTextEdit()
        self.log_box.setReadOnly(True)

        self.refresh_btn = QPushButton("🔄 Refresh")
        self.create_btn = QPushButton("Create Instance")
        self.delete_btn = QPushButton("Delete Instance")
        self.prepare_btn = QPushButton("Prepare\nInstance")
        self.setapp_btn = QPushButton("Set App")
        self.delete_account_btn = QPushButton("Delete Device\nAccount.xml")
        self.browse_btn = QPushButton("Extract Game\nData Files")
        self.inject2_btn = QPushButton("Inject Game\nData Files")
        self.extract_btn = QPushButton("Extract Account")
        self.inject_btn = QPushButton("Inject Account")

        self.init_ui()
        self.refresh()

    # ========================
    # UI
    # ========================
    def init_ui(self):
        labels = QHBoxLayout()
        labels.addWidget(QLabel("LDPlayer Instances"))
        labels.addWidget(QLabel("ADB Devices"))
        self.layout.addLayout(labels)

        lists = QHBoxLayout()
        lists.addWidget(self.instance_list)
        lists.addWidget(self.device_list)
        self.layout.addLayout(lists)

        btns1 = QHBoxLayout()
        btns1.addWidget(self.create_btn)
        btns1.addWidget(self.delete_btn)
        btns1.addWidget(self.prepare_btn)
        btns1.addWidget(self.setapp_btn)
        btns1.addStretch()
        btns1.addWidget(self.delete_account_btn)
        btns1.addStretch()
        btns1.addWidget(self.browse_btn)
        btns1.addWidget(self.inject2_btn)
        btns1.addWidget(self.extract_btn)
        btns1.addWidget(self.inject_btn)
        self.layout.addLayout(btns1)

        self.layout.addWidget(self.refresh_btn)
        self.layout.addWidget(QLabel("LOG"))
        self.layout.addWidget(self.log_box)

        self.refresh_btn.clicked.connect(self.refresh)
        self.create_btn.clicked.connect(self.create_instance)
        self.delete_btn.clicked.connect(self.delete_instance)
        self.prepare_btn.clicked.connect(self.prepare_instance)
        self.setapp_btn.clicked.connect(self.set_app)
        self.delete_account_btn.clicked.connect(self.delete_account)
        self.browse_btn.clicked.connect(self.browse_app_files)
        self.inject2_btn.clicked.connect(self.inject_game_data)
        self.extract_btn.clicked.connect(self.extract_account)
        self.inject_btn.clicked.connect(self.inject_account)
    def delete_account(self):
        device = self.get_device()
        if not device:
            return

        reply = QMessageBox.question(
            self,
            "Delete Account",
            "deviceAccount.xml 을 삭제하면 이 인스턴스는 로그아웃됩니다.\n계속하시겠습니까?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )

        if reply != QMessageBox.StandardButton.Yes:
            return

        self.worker = AdbWorker(self._delete_account_task, device)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()

    def browse_app_files(self):
        device = self.get_device()
        if not device:
            return

        base_dirs = [
            f"/data/data/{PACKAGE}",
            f"/sdcard/Android/data/{PACKAGE}",
            f"/sdcard/Android/obb/{PACKAGE}"
        ]

        self.worker = AdbWorker(self._browse_app_files_task, device, base_dirs)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()
    def inject_game_data(self):
        device = self.get_device()
        if not device:
            return

        tars, _ = QFileDialog.getOpenFileNames(
            self,
            "Select Game Data TAR",
            "",
            "*.tar"
        )
        if not tars:
            return

        self.worker = AdbWorker(self._inject_game_data_task, device, tars)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.finished_signal.connect(self.log_box.append)
        self.worker.start()

    # ========================
    # SETTINGS
    # ========================
    def open_settings(self):
        dlg = SettingsDialog(self.settings, self)
        dlg.exec()
        global ADB_PATH, LDCONSOLE
        ADB_PATH = self.settings.value("adb_path", ADB_PATH)
        LDCONSOLE = self.settings.value("ldconsole_path", LDCONSOLE)

    # ========================
    # REFRESH
    # ========================
    def refresh(self):
        self.instance_list.clear()
        self.device_list.clear()
        self.log_box.clear()
        
        for idx, name in list_instances():
            self.instance_list.addItem(f"{idx} - {name}")

        self.worker = AdbWorker(self._refresh_devices_task)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.finished_signal.connect(self._update_device_list)
        self.worker.start()

    def _refresh_devices_task(self, log_callback=None):
        devs = list_adb_devices(retry=10)
        if log_callback:
            for d in devs:
                log_callback.emit(f"▶ Found device: {d}")
        return ",".join(devs)

    def _update_device_list(self, dev_str):
        for d in dev_str.split(","):
            if d:
                self.device_list.addItem(d)

    # ========================
    # INSTANCE
    # ========================
    def get_instance_index(self):
        item = self.instance_list.currentItem()
        if not item:
            QMessageBox.warning(self, "Error", "LDPlayer 인스턴스를 선택하세요.")
            return None
        return item.text().split(" - ")[0]

    def create_instance(self):
        name, ok = QInputDialog.getText(self, "Create Instance", "Instance Name")
        if not ok or not name:
            return
        self.worker = AdbWorker(self._create_instance_task, name)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.finished_signal.connect(lambda _: self.refresh())
        self.worker.start()

    def _create_instance_task(self, name, log_callback=None):
        run([LDCONSOLE, "add", "--name", name], log_callback)
        return "Instance Created"

    def delete_instance(self):
        idx = self.get_instance_index()
        if not idx:
            return
        reply = QMessageBox.question(
            self,
            "WARNING",
            f"인스턴스 {idx} 삭제 (복구 불가)",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if reply != QMessageBox.StandardButton.Yes:
            return
        self.worker = AdbWorker(self._delete_instance_task, idx)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.finished_signal.connect(lambda _: self.refresh())
        self.worker.start()

    def _delete_instance_task(self, idx, log_callback=None):
        run([LDCONSOLE, "remove", "--index", idx], log_callback)
        return "Instance Deleted"

    # ========================
    # EXISTING FEATURES (유지)
    # ========================
    def prepare_instance(self):
        idx = self.get_instance_index()
        if not idx:
            return
        self.worker = AdbWorker(self._prepare_instance_task, idx)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()

    def _prepare_instance_task(self, idx, log_callback=None):
        run([LDCONSOLE, "modify", "--index", idx, "--root", "1"], log_callback)
        run([LDCONSOLE, "modify", "--index", idx, "--adb", "1"], log_callback)
        run([LDCONSOLE, "modify", "--index", idx, "--resolution", "960,540,280"], log_callback)
        run([LDCONSOLE, "modify", "--index", idx, "--lockwindow", "1"], log_callback)
        run([LDCONSOLE, "launch", "--index", idx], log_callback)

    def get_device(self):
        item = self.device_list.currentItem()
        if not item:
            QMessageBox.warning(self, "Error", "ADB 디바이스를 선택하세요.")
            return None
        return item.text()

    def set_app(self):
        device = self.get_device()
        if not device:
            return
        apk, _ = QFileDialog.getOpenFileName(self, "Select APK", "", "*.apk")
        if not apk:
            return
        self.worker = AdbWorker(self._set_app_task, device, apk)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()

    def _set_app_task(self, device, apk, log_callback=None):
        run([ADB_PATH, "-s", device, "root"], log_callback)
        time.sleep(1)
        run([ADB_PATH, "-s", device, "install", "-r", apk], log_callback)
        run([ADB_PATH, "-s", device, "shell", "appops", "set", PACKAGE, "SYSTEM_ALERT_WINDOW", "allow"], log_callback=log_callback)
        run([ADB_PATH, "-s", device, "shell", "appops", "set", PACKAGE, "RUN_IN_BACKGROUND", "allow"], log_callback=log_callback)
        run([ADB_PATH, "-s", device, "shell", "appops", "set", PACKAGE, "WAKE_LOCK", "allow"], log_callback=log_callback)

        # Android 11+ 알림 권한
        sdk = run([ADB_PATH, "-s", device, "shell", "getprop", "ro.build.version.sdk"], log_callback=log_callback).stdout.strip()
        if int(sdk) >= 33:
            run([ADB_PATH, "-s", device, "shell", "pm", "grant", PACKAGE, "android.permission.POST_NOTIFICATIONS"], log_callback=log_callback)
        else:
            if log_callback:
                log_callback.emit(f"▶ SDK {sdk}: POST_NOTIFICATIONS not supported (skip)")
        run([ADB_PATH, "-s", device, "shell", "am", "start", "-n",
             f"{PACKAGE}/com.unity3d.player.UnityPlayerActivity"], log_callback)
        time.sleep(2)
        run([ADB_PATH, "-s", device, "shell", "am", "force-stop", PACKAGE], log_callback)

    def extract_account(self):
        device = self.get_device()
        if not device:
            return
        path, _ = QFileDialog.getSaveFileName(self, "Save XML", "", "*.xml")
        if not path:
            return
        self.worker = AdbWorker(self._extract_account_task, device, path)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()

    def _extract_account_task(self, device, path, log_callback=None):
        run([ADB_PATH, "-s", device, "root"], log_callback)
        run([ADB_PATH, "-s", device, "shell", f"cp {REMOTE_XML} /sdcard/deviceAccount.xml"], log_callback)
        run([ADB_PATH, "-s", device, "pull", "/sdcard/deviceAccount.xml", path], log_callback)

    def inject_account(self):
        device = self.get_device()
        if not device:
            return
        xml, _ = QFileDialog.getOpenFileName(self, "Select XML", "", "*.xml")
        if not xml:
            return
        self.worker = AdbWorker(self._inject_account_task, device, xml)
        self.worker.log_signal.connect(self.log_box.append)
        self.worker.start()

    def _inject_account_task(self, device, xml, log_callback=None):
        run([ADB_PATH, "-s", device, "root"], log_callback)
        run([ADB_PATH, "-s", device, "shell", "am", "force-stop", PACKAGE], log_callback)
        run([ADB_PATH, "-s", device, "push", xml, "/sdcard/deviceAccount.xml"], log_callback)
        run([ADB_PATH, "-s", device, "shell", f"cp /sdcard/deviceAccount.xml '{REMOTE_XML}'"], log_callback)
        run([ADB_PATH, "-s", device, "shell", "chmod 600 " + REMOTE_XML], log_callback)
    def _browse_app_files_task(self, device, base_dirs, log_callback=None):
        run([ADB_PATH, "-s", device, "root"], log_callback)

        timestamp = time.strftime("%Y%m%d_%H%M%S")

        for base in base_dirs:
            safe_name = base.replace("/", "_").strip("_")
            remote_tar = f"/sdcard/{safe_name}.tar"
            local_tar = f"{safe_name}_{timestamp}.tar"

            if log_callback:
                log_callback.emit(f"\n===== EXTRACT {base} =====")

            # 존재 여부 확인
            chk = run(
                [ADB_PATH, "-s", device, "shell", f"ls {base}"],
                log_callback=None
            )
            if chk.returncode != 0:
                if log_callback:
                    log_callback.emit("❌ Path not found (skip)")
                continue

            # tar 생성 (기기)
            run(
                [ADB_PATH, "-s", device, "shell",
                f"tar -cf {remote_tar} -C {base} ."],
                log_callback
            )

            # PC로 pull (현재 폴더)
            run(
                [ADB_PATH, "-s", device, "pull", remote_tar, local_tar],
                log_callback
            )

            # 기기 쪽 임시 tar 삭제
            run(
                [ADB_PATH, "-s", device, "shell", f"rm {remote_tar}"],
                log_callback
            )

            if log_callback:
                log_callback.emit(f"✅ Saved: {local_tar}")

        return "Extract finished"
    

    def _inject_game_data_task(self, device, tar_paths, log_callback=None):
        log_callback.emit("▶ Game data inject task started")
        log_callback.emit(f"▶ TAR count: {len(tar_paths)}")

        run([ADB_PATH, "-s", device, "root"], log_callback)

        app_uid = None  # /data/data 주입 시에만 사용

        for tar_path in tar_paths:
            tar_name = os.path.basename(tar_path)

            # -------------------------------
            # tar 타입 판별
            # -------------------------------
            if "data_data" in tar_name:
                target_dir = f"/data/data/{PACKAGE}"
                need_su = True
                need_chown = True

                if not app_uid:
                    app_uid = get_app_uid(device, log_callback)
                    if not app_uid:
                        log_callback.emit("❌ Failed to get app UID")
                        continue

            elif "Android_data" in tar_name:
                target_dir = f"/sdcard/Android/data/{PACKAGE}"
                need_su = True
                need_chown = False

            elif "Android_obb" in tar_name:
                target_dir = f"/sdcard/Android/obb/{PACKAGE}"
                need_su = True
                need_chown = False

            else:
                log_callback.emit(f"❌ Unknown tar type: {tar_name}")
                continue

            remote_tar = f"/sdcard/{tar_name}"

            log_callback.emit(f"\n===== INJECT {tar_name} =====")
            log_callback.emit(f"▶ Target path: {target_dir}")

            # -------------------------------
            # 앱 종료
            # -------------------------------
            run(
                [ADB_PATH, "-s", device, "shell", "am", "force-stop", PACKAGE],
                log_callback
            )

            # -------------------------------
            # tar push
            # -------------------------------
            run(
                [ADB_PATH, "-s", device, "push", tar_path, remote_tar],
                log_callback
            )

            # -------------------------------
            # 기존 데이터 제거 + 디렉토리 생성
            # -------------------------------
            if need_su:
                run(
                    [
                        ADB_PATH, "-s", device, "shell",
                        f'su -c "rm -rf {target_dir} && mkdir -p {target_dir}"'
                    ],
                    log_callback
                )
            else:
                run([ADB_PATH, "-s", device, "shell", f"rm -rf {target_dir}"], log_callback)
                run([ADB_PATH, "-s", device, "shell", f"mkdir -p {target_dir}"], log_callback)

            # -------------------------------
            # tar 풀기
            # -------------------------------
            if need_su:
                run(
                    [
                        ADB_PATH, "-s", device, "shell",
                        f'su -c "tar -xf {remote_tar} -C {target_dir}"'
                    ],
                    log_callback
                )
            else:
                run(
                    [
                        ADB_PATH, "-s", device, "shell",
                        f"tar -xf {remote_tar} -C {target_dir}"
                    ],
                    log_callback
                )

            log_callback.emit("▶ Tar extracted")

            # -------------------------------
            # 권한 복구 (/data/data 전용)
            # -------------------------------
            if need_chown:
                run(
                    [
                        ADB_PATH, "-s", device, "shell",
                        f'su -c "chown -R {app_uid}:{app_uid} {target_dir} && chmod -R 700 {target_dir}"'
                    ],
                    log_callback
                )

            # -------------------------------
            # 임시 tar 삭제
            # -------------------------------
            run(
                [ADB_PATH, "-s", device, "shell", f"rm {remote_tar}"],
                log_callback
            )

            log_callback.emit(f"✅ Injected: {tar_name}")

        log_callback.emit("🎉 Game data inject finished")
        return "Game data inject finished"


    def _delete_account_task(self, device, log_callback=None):
        run([ADB_PATH, "-s", device, "root"], log_callback)
        run([ADB_PATH, "-s", device, "shell", "am", "force-stop", PACKAGE], log_callback)

        # 파일 존재 확인
        chk = run(
            [ADB_PATH, "-s", device, "shell", f"ls '{REMOTE_XML}'"],
            log_callback
        )

        if chk.returncode != 0:
            log_callback.emit("ℹ deviceAccount.xml not found (already removed)")
            return "Done"

        # 삭제
        run(
            [ADB_PATH, "-s", device, "shell", f"rm '{REMOTE_XML}'"],
            log_callback
        )

        log_callback.emit("✅ deviceAccount.xml deleted")
        return "Account cleared"


# ========================
# MAIN
# ========================
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = AccountTool()
    w.show()
    sys.exit(app.exec())
