"""
Switch Downgrader — Utility functions.
Handles subprocess execution, progress tracking, file operations, and error logging.
"""
import sys
import os
import re
import stat
import shutil
import hashlib
import subprocess
import traceback
import datetime


# --- Error Logging ---
def log_uncaught_exceptions(ex_cls, ex, tb):
    log_file = os.path.join(os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else __file__), "crash_log.txt")
    with open(log_file, "a") as f:
        f.write(f"\n{'='*60}\n")
        f.write(f"CRASH: {datetime.datetime.now()}\n")
        f.write("".join(traceback.format_exception(ex_cls, ex, tb)))
    sys.__excepthook__(ex_cls, ex, tb)

sys.excepthook = log_uncaught_exceptions


def force_remove_readonly(func, path, exc_info):
    """Error handler for shutil.rmtree to handle read-only files."""
    os.chmod(path, stat.S_IWRITE)
    func(path)


def safe_remove_directory(directory_path):
    """Safely remove a directory, handling read-only files."""
    try:
        if os.path.exists(str(directory_path)):
            shutil.rmtree(str(directory_path), onerror=force_remove_readonly)
        return True
    except Exception as e:
        print(f"WARNING: Could not fully remove {directory_path}: {e}")
        return False


def set_app_user_model_id(app_id):
    """Set the Windows App User Model ID to fix taskbar icon."""
    try:
        import ctypes
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
    except Exception:
        pass


def get_resource_path():
    """Get the path for bundled resources (locales, lib, images)."""
    import sys, os
    if getattr(sys, 'frozen', False):
        return getattr(sys, '_MEIPASS', os.path.dirname(sys.executable))
    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def get_base_path():
    """Get base path — works both for script and PyInstaller frozen exe."""
    import sys, os
    if getattr(sys, 'frozen', False):
        return os.path.dirname(sys.executable)
    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def run_command(command, cwd=None, log_func=None):
    """Run a subprocess command. Returns (return_code, output_text)."""
    try:
        process = subprocess.Popen(
            command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            text=True, creationflags=subprocess.CREATE_NO_WINDOW,
            cwd=cwd, bufsize=1, universal_newlines=True
        )
        output = []
        for line in iter(process.stdout.readline, ''):
            clean = line.strip()
            if not clean:
                continue
            output.append(clean)
            if log_func:
                log_func(clean)
        process.stdout.close()
        rc = process.wait()
        return rc, "\n".join(output)
    except Exception as e:
        msg = f"FATAL ERROR: Failed to execute command. {e}"
        if log_func:
            log_func(msg)
        return -1, str(e)


def run_command_with_progress(command, task_name="Processing", log_func=None, progress_func=None):
    """Run a command and parse percentage progress (e.g., 7z extraction)."""
    try:
        process = subprocess.Popen(
            command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            text=True, creationflags=subprocess.CREATE_NO_WINDOW,
            bufsize=1, universal_newlines=True
        )
        output = []
        progress_regex = re.compile(r"(\d+)\s*%\s*\d*")
        for line in iter(process.stdout.readline, ''):
            clean = line.strip()
            if not clean:
                continue
            match = progress_regex.search(clean)
            if match and progress_func:
                pct = int(match.group(1))
                bar_len = 25
                filled = int(bar_len * pct / 100)
                bar = '█' * filled + '-' * (bar_len - filled)
                progress_func(f"{task_name}: [{bar}] {pct}%")
            else:
                output.append(clean)
                if log_func:
                    log_func(clean)
        process.stdout.close()
        rc = process.wait()
        if log_func:
            log_func(f"--- {task_name} finished.")
        return rc, "\n".join(output)
    except Exception as e:
        if log_func:
            log_func(f"FATAL ERROR: {e}")
        return -1, str(e)


def run_and_interrupt_flash(command, partition_name, target_mb, log_func=None):
    """Run a flash command and terminate once a target MB threshold is reached."""
    if log_func:
        log_func(f"--- Starting partial flash for {partition_name} ({target_mb}MB target)...")
    try:
        process = subprocess.Popen(
            command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            text=True, creationflags=subprocess.CREATE_NO_WINDOW,
            bufsize=1, universal_newlines=True
        )
        progress_regex = re.compile(rf"Restoring to {partition_name}... (\d+\.\d+)\s*MB")
        for line in iter(process.stdout.readline, ''):
            clean = line.strip()
            if not clean:
                continue
            if log_func:
                log_func(clean)
            match = progress_regex.search(clean)
            if match and float(match.group(1)) >= target_mb:
                if log_func:
                    log_func(f"--- SUCCESS: Reached target. Terminating flash...")
                process.terminate()
                break
        process.stdout.close()
        process.wait()
        if log_func:
            log_func(f"--- Partial flash for {partition_name} complete.")
        return 0
    except Exception as e:
        if log_func:
            log_func(f"FATAL ERROR during flash: {e}")
        return -1


def copy_with_progress(src_path, dest_path, task_name="Copying file", log_func=None, progress_func=None):
    """Copy a large file with progress tracking."""
    try:
        total = os.path.getsize(src_path)
        copied = 0
        chunk = 4 * 1024 * 1024  # 4MB chunks
        with open(src_path, 'rb') as fsrc, open(dest_path, 'wb') as fdst:
            while True:
                buf = fsrc.read(chunk)
                if not buf:
                    break
                fdst.write(buf)
                copied += len(buf)
                if progress_func and total > 0:
                    pct = int(copied * 100 / total)
                    bar_len = 25
                    filled = int(bar_len * pct / 100)
                    bar = '█' * filled + '-' * (bar_len - filled)
                    progress_func(f"{task_name}: [{bar}] {pct}%")
        if log_func:
            log_func(f"--- {task_name} complete.")
        return True
    except Exception as e:
        if log_func:
            log_func(f"ERROR copying file: {e}")
        return False


def check_disk_space(required_gb=60, log_func=None, temp_path=None):
    """Check if the target drive has enough free space."""
    try:
        import psutil
        target = temp_path if temp_path and os.path.isdir(temp_path) else os.environ.get('TEMP', 'C:\\')
        usage = psutil.disk_usage(target)
        free_gb = usage.free / (1024**3)
        if free_gb < required_gb:
            if log_func:
                log_func(f"ERROR: Not enough disk space. Need {required_gb}GB, have {free_gb:.1f}GB free.")
            return False
        if log_func:
            log_func(f"Disk space OK: {free_gb:.1f}GB free.")
        return True
    except Exception as e:
        if log_func:
            log_func(f"WARNING: Could not check disk space: {e}")
        return True


def ask_directory_old_style(title="Select Folder"):
    """
    Opens the classic Windows 'Browse For Folder' dialog.
    This dialog shows a tree of folders and inherently doesn't show files,
    preventing user confusion when selecting directories like firmware.
    """
    try:
        import ctypes
        from ctypes import wintypes
        
        class BROWSEINFOW(ctypes.Structure):
            _fields_ = [
                ("hwndOwner", wintypes.HWND),
                ("pidlRoot", wintypes.LPVOID),
                ("pszDisplayName", wintypes.LPWSTR),
                ("lpszTitle", wintypes.LPCWSTR),
                ("ulFlags", wintypes.UINT),
                ("lpfn", wintypes.LPVOID),
                ("lParam", wintypes.LPARAM),
                ("iImage", wintypes.INT)
            ]
            
        shell32 = ctypes.windll.shell32
        ole32 = ctypes.windll.ole32
        
        shell32.SHBrowseForFolderW.restype = ctypes.c_void_p
        
        bi = BROWSEINFOW()
        bi.hwndOwner = None 
        bi.lpszTitle = title
        # BIF_RETURNONLYFSDIRS (0x0001) | BIF_NEWDIALOGSTYLE (0x0040)
        bi.ulFlags = 0x0040 | 0x0001
        
        pidl = shell32.SHBrowseForFolderW(ctypes.byref(bi))
        if pidl:
            path_buffer = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
            success = shell32.SHGetPathFromIDListW(ctypes.c_void_p(pidl), path_buffer)
            ole32.CoTaskMemFree(ctypes.c_void_p(pidl))
            if success:
                return path_buffer.value
        return ""
    except Exception:
        # Fallback to standard tkinter dialog if ctypes fails on a weird system
        from tkinter import filedialog
        return filedialog.askdirectory(title=title)
