#!D:\IDE\Anaconda3\envs\Microscope_Scan python
# -*- coding: UTF-8 -*-
"""
 @Author: Administrator
 @FileName: AppUtils.py
 @DateTime: 2025/3/23 11:45
 @SoftWare: PyCharm
 @Author  ：xsh
"""
from AppSettings.Settings import IsDebug
from importlib import import_module

from PyQt5 import Qt
import inspect
import traceback
import time
import sys
import json
import psutil
import subprocess
class AppUtils:
    @staticmethod
    def GetPackageType(module_name: str, package_name: str) -> type:
        if not isinstance(module_name, str) or not isinstance(package_name, str):
            return None
        _module = None
        try:
            _module = import_module(module_name, package=package_name)
        except Exception as e:
            AppUtils.print_Exception(e)
            return None
        instance: object = getattr(_module, package_name, None)
        return instance

    @staticmethod
    def GetModuleInstance(module_name: str, package_name: str, isSingle: bool = False, *args, **kwargs):
        if not isinstance(module_name, str) or not isinstance(package_name, str):
            return None
        _module = None
        try:
            _module = import_module(name=module_name, package=package_name)
        except Exception as e:
            AppUtils.print_Exception(e)
            return None
        instance: object = getattr(_module, package_name, None)
        if instance is None:
            return None
        return instance(*args, **kwargs) if not isSingle else instance.GetInstance(*args, **kwargs)

    @staticmethod
    def getAllProcesses():
        processes: list[dict[str, object]] = []
        processes_iter = psutil.process_iter()
        proc: psutil.Process
        for proc in processes_iter:
            try:
                processes.append({"pid": proc.pid, "name": proc.name()})
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
        return processes
    @staticmethod
    def terminate_process_by_pid(pid: int, timeout: int = 5):
        proc = psutil.Process(pid)
        proc.terminate()
        proc.wait(timeout=timeout)  # 等待进程终止，最多等待3秒
        # print(f"Terminated process: PID: {pid}")

    @staticmethod
    def getFilterProcesses(name: str = None) -> list[dict[str, object]]:
        processes: list[dict[str, object]] = []
        processes_iter = psutil.process_iter(['pid', 'name'])
        proc: psutil.Process
        for proc in processes_iter:
            if isinstance(name, str) and name.lower() not in proc.info['name'].lower():
                continue
            try:
                processes.append({"pid":  proc.info['pid'], "name":  proc.info['name']})
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
        return processes

    @staticmethod
    def start_process(filename: str) -> dict[str, object]:
        result: dict[str, object] = {"pid": None, "name": None}
        info: Qt.QFileInfo = Qt.QFileInfo(filename)
        if not info.exists():
            return result
        try:
            process = subprocess.Popen(info.absoluteFilePath(), shell=True)
            # print(f"Started process: {info.completeBaseName()} (PID: {process.pid})")
            result = {"pid": process.pid, "name": info.completeBaseName()}
        except Exception as e:
            print(f"Failed to start process: {info.completeBaseName()}. Error: {e}")
        return result

    @staticmethod
    def isEmpty(string: str) -> bool:
        """检查字符串是否为空或仅包含空白字符"""
        return not string or string.isspace() or len(string) <= 0 or len(string.replace(" ", "")) <= 0


    @staticmethod
    def print_current_function_info(message: str):
        """打印当前函数名称和行号"""
        frame = inspect.currentframe().f_back
        print(f"Current function: {frame.f_code.co_name}, Line: {frame.f_lineno} Message: {message if message is not None else '' }" )

    @staticmethod
    def current_function_info(message: str) -> str:
        frame = inspect.currentframe().f_back
        return f"[function: {frame.f_code.co_name}]\t[Line: {frame.f_lineno} ]\t {message}"

    @staticmethod
    def print_Exception(e: Exception, message: str = ""):
        warning_string = AppUtils.toString(e)
        # from AppCore.AppLogHelper import logSYSTEM
        # logSYSTEM << warning_string
        if AppUtils.isEmpty(message):
            print(warning_string)
        else:
            print(f"{warning_string}\tMessage: {message}")
    @staticmethod
    def toString(e: Exception) -> str:
        # 获取当前的异常信息
        exc_type, exc_obj, tb = type(e), e, e.__traceback__

        # 获取调用栈信息
        frame = tb.tb_frame
        lineno = tb.tb_lineno
        func_name = frame.f_code.co_name
        # 获取完整的异常信息
        full_traceback = traceback.format_exception(exc_type, exc_obj, tb)
        # 构建错误信息字符串
        error_info: list[str] = [f"Error: {str(e)}", f"Function: {func_name}", f"Line: {lineno}",
                                 f"Traceback:{' '.join(full_traceback)}"]
        return "\t".join(error_info)
    
    @staticmethod
    def toSourcePath() -> str:
        return f"{Qt.QFileInfo(sys.argv[0]).absolutePath()}/Resource/"
    
    @staticmethod
    def toSourceFile(file: str) -> str:
        if not isinstance(file, str):
            return ''
        if file.find(":") >= 0:  # 如果文件路径已经是绝对路径，则直接返回
            return Qt.QFileInfo(file).absoluteFilePath()
        if file[0:2] == '//' or file[0:2] == r'\\':
            return Qt.QFileInfo(file).absoluteFilePath()
        return Qt.QFileInfo(f"{AppUtils.toSourcePath()}{file}").absoluteFilePath()

    @staticmethod
    def absoluteFilePath(file: str) -> str:
        if not isinstance(file, str):
            return ''
        if file.find(":") >= 0:  # 如果文件路径已经是绝对路径，则直接返回
            return Qt.QFileInfo(file).absoluteFilePath()
        if file[0:2] == '//' or file[0:2] == r'\\':
            return Qt.QFileInfo(file).absoluteFilePath()
        return Qt.QFileInfo(f"{Qt.QFileInfo(sys.argv[0]).absolutePath()}/{file}").absoluteFilePath()

    import subprocess

    @staticmethod
    def open_folder_safely(folder_path):
        import os
        import sys
        """
        安全地打开文件夹，包含错误处理
        """
        try:
            # 检查文件夹是否存在
            if not os.path.exists(folder_path):
                print(f"文件夹不存在: {folder_path}")
                return False

            # 检查是否为文件夹
            if not os.path.isdir(folder_path):
                print(f"路径不是文件夹: {folder_path}")
                return False

            # 根据操作系统打开文件夹
            if sys.platform == "win32":
                os.startfile(folder_path)
            elif sys.platform == "darwin":
                subprocess.run(["open", folder_path])
            else:
                subprocess.run(["xdg-open", folder_path])

            return True
        except Exception as e:
            print(f"打开文件夹时出错: {e}")
            return False

    @staticmethod
    def field_to_yaml(self) -> str:
        string: str = ''
        try:
            from AppUtils.ConvertUtils import ConvertUtils
            buffer: dict[str, object] = {}
            for index, _property in enumerate(self.__field__):
                buffer[_property] = getattr(self, _property, None)
            string = ConvertUtils.toYaml(buffer)
        except Exception as e:
            AppUtils.print_Exception(e)
        return string

    @staticmethod
    def field_to_json(self) -> str:
        string: str = ''
        try:
            from AppUtils.ConvertUtils import ConvertUtils
            buffer: dict[str, object] = {}
            for index, _property in enumerate(self.__field__):
                buffer[_property] = getattr(self, _property, None)
            string = ConvertUtils.toJson(buffer)
        except Exception as e:
            AppUtils.print_Exception(e)
        return string

    @staticmethod
    def field_to_text(self) -> str:
        string: str = ''
        try:
            for index, _property in enumerate(self.__field__):
                string += f'{_property}:{getattr(self, _property, None)}\r\n'
        except Exception as e:
            AppUtils.print_Exception(e)
        return string

    @staticmethod
    def get_fields_information(self, fmt: str) -> str:
        if fmt.lower() == 'txt':
            return AppUtils.field_to_text(self)
        elif fmt.lower() == 'json':
            return AppUtils.field_to_json(self)
        else:
            return AppUtils.field_to_yaml(self)

    @staticmethod
    def save_string_to_file(file_path: str, content: str):
        """将字符串保存到本地文件"""
        info: Qt.QFileInfo = Qt.QFileInfo(AppUtils.toSourceFile(file_path))
        if not info.dir().exists():
            return info.dir().mkpath(info.absolutePath())
        file = Qt.QFile(info.absoluteFilePath())
        if file.open(Qt.QIODevice.WriteOnly | Qt.QIODevice.Text):
            out = Qt.QTextStream(file)
            out.setCodec('UTF-8')
            out << content
            out.flush()
            file.close()
            return True
        else:
            return False

    @staticmethod
    def read_string_from_file(file_path: str) -> str:
        """从本地文件读取内容到字符串"""
        info: Qt.QFileInfo = Qt.QFileInfo(AppUtils.toSourceFile(file_path))
        if not info.exists():
            return None
        file = Qt.QFile(info.absoluteFilePath())
        if file.open(Qt.QIODevice.ReadOnly | Qt.QIODevice.Text):
            in_stream = Qt.QTextStream(file)
            in_stream.setCodec('UTF-8')
            content = in_stream.readAll()
            file.close()
            return content
        else:
            return None

    @staticmethod
    def IsSocketPortAvailable(address: str, port: int) -> bool:
        """判断当前IP 和 端口是否可联接"""
        try:
            if not isinstance(address, str) or not isinstance(port, (int, float)):
                return False
            Socket: Qt.QTcpSocket = Qt.QTcpSocket()
            Socket.setPeerAddress(Qt.QHostAddress(address))
            Socket.setPeerPort(int(port))
            IsAvailable = Socket.waitForConnected(1000)
            if IsAvailable:
                Socket.disconnectFromHost()
                return True
            else:
                return False
        except Exception as e:
            AppUtils.print_Exception(e)
        return False
    

def log_exceptions(func):
    def wrapper(*args, **kwargs):
        try:
            if IsDebug:
                # start_time = time.time()
                result = func(*args, **kwargs)
                # end_time = time.time()
                # print(f"Function '{func.__name__}' executed in {end_time - start_time:.4f} seconds")
                return result
            else:
                result = func(*args, **kwargs)
                return result
        except Exception as e:
            from AppLog.logHelper  import logSYSTEM
            message = AppUtils.toString(e)
            logSYSTEM << message
            print(message)
            return None  # 重新抛出异常，以便调用者可以处理
    return wrapper



