import os
import sys
import json
import shutil
import subprocess
import hashlib
from pathlib import Path
from typing import List, Dict, Optional, Set, Tuple

from alayaflow.common.config import settings


class EnvManager:
    """
    使用 uv (Rust-based tool) 替代 venv + pip。
    提供极速的环境创建和 Python 版本管理。
    增强功能：根据requirements自动匹配现有环境
    """

    def __init__(self):
        self.base_dir = settings.envs_dir
        if not os.path.exists(self.base_dir):
            os.makedirs(self.base_dir)

        # 检查 uv 是否存在
        if shutil.which("uv") is None:
            print("[Warning] 'uv' command not found! Please install uv first.")
            # 在实际桌面应用中，你应该将 uv 的二进制文件打包在你的应用里，
            # 并在这里指定绝对路径，例如: self.uv_bin = "./bin/uv.exe"
            self.uv_bin = "uv"
        else:
            self.uv_bin = "uv"

        # 元数据存储
        self.metadata_file = os.path.join(self.base_dir, "envs_metadata.json")
        self.env_metadata = self._load_metadata()

        # 初始化时扫描所有现有环境并更新包信息
        self._scan_existing_envs()

    def _load_metadata(self) -> Dict:
        """加载环境元数据"""
        if os.path.exists(self.metadata_file):
            try:
                with open(self.metadata_file, 'r') as f:
                    return json.load(f)
            except json.JSONDecodeError:
                return {}
        return {}

    def _save_metadata(self):
        """保存环境元数据"""
        os.makedirs(os.path.dirname(self.metadata_file), exist_ok=True)
        with open(self.metadata_file, 'w') as f:
            json.dump(self.env_metadata, f, indent=2)

    # def get_venv_path(self, workflow_id: str, version: str) -> str:
    #     return os.path.join(self.base_dir, f"{workflow_id}_{version}")

    def get_venv_path_by_name(self, env_name: str) -> str:
        return os.path.join(self.base_dir, env_name)

    def get_python_executable(self, env_path: str) -> str:
        # uv 创建的 venv 结构和标准 venv 一样
        if sys.platform == "win32":
            return os.path.join(env_path, "Scripts", "python.exe")
        return os.path.join(env_path, "bin", "python")

    def _get_python_version(self, env_path: str) -> str:
        """使用subprocess调用python print获取环境的Python版本"""
        python_exe = self.get_python_executable(env_path)
        try:
            cmd = [python_exe, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            return result.stdout.strip()
        except:
            return "unknown"

    def _scan_existing_envs(self):
        """扫描现有环境（子目录存在且有python_exe）并更新包信息"""
        print("[UV] Scanning existing environments...")
        for env_dir in os.listdir(self.base_dir):
            # 跳过元数据文件
            if env_dir == "envs_metadata.json":
                continue
            env_path = os.path.join(self.base_dir, env_dir)
            # todo: consider if the following logic is clear. Seems not good
            if os.path.isdir(env_path):
                # 检查是否是有效的虚拟环境
                python_exe = self.get_python_executable(env_path)
                if os.path.exists(python_exe):
                    if env_dir not in self.env_metadata:
                        # 新发现的环境，生成包信息
                        print(f"[UV] Found new environment: {env_dir}")
                        self._update_env_packages(env_dir)
        self._save_metadata()

    def _update_env_packages(self, env_name: str):
        """更新指定环境的包信息"""
        env_path = self.get_venv_path_by_name(env_name)

        try:
            # 获取已安装的包列表
            cmd = [self.uv_bin, "pip", "freeze", "-p", env_path]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)

            packages = {}
            for line in result.stdout.strip().split('\n'):
                if line and '==' in line:
                    name, version = line.split('==', 1)
                    packages[name] = version

            # 更新元数据
            self.env_metadata[env_name] = {
                "path": env_path,
                "packages": packages,
                "python_version": self._get_python_version(env_path),
                # "requirements": []  # 初始安装的requirements（可选的）
            }

            print(f"[UV] Updated packages for {env_name}: {len(packages)} packages")

        except subprocess.CalledProcessError as e:
            print(f"[UV] Failed to scan packages for {env_name}: {e}")

    def _parse_requirement(self, req_str: str) -> Tuple[str, str]:
        """解析单个requirement字符串，返回包名和版本约束"""
        # 移除注释和空格
        req_str = req_str.split('#')[0].strip()
        if not req_str:
            return None, None

        # 解析包名和版本
        import re
        # 匹配包名（允许字母、数字、点、下划线、连字符）
        name_match = re.match(r'^([a-zA-Z0-9._-]+)', req_str)
        if not name_match:
            return None, None

        name = name_match.group(1).lower()
        version_spec = req_str[len(name):].strip()

        # 标准化版本约束
        if version_spec:
            # 移除多余的空格
            version_spec = version_spec.replace(' ', '')
        else:
            version_spec = "any"  # 未指定版本

        return name, version_spec

    def _check_package_compatibility(self, installed_ver: str, required_spec: str) -> bool:
        """检查已安装版本是否满足要求"""
        if required_spec == "any":
            return True

        from packaging.version import parse, Version
        from packaging.specifiers import SpecifierSet

        try:
            # 解析已安装版本
            installed = parse(installed_ver)
            if not isinstance(installed, Version):
                return False

            # 解析版本要求
            specifier = SpecifierSet(required_spec)
            return installed in specifier

        except Exception as e:
            print(f"[EM] Version check error: {e}")
            return False
            # 如果解析失败，进行字符串比较作为后备
        #     try:
        #         # 简单的比较逻辑
        #         if required_spec.startswith("=="):
        #             return installed_ver == required_spec[2:]
        #         elif required_spec.startswith(">="):
        #             return installed_ver >= required_spec[2:]
        #         elif required_spec.startswith("<="):
        #             return installed_ver <= required_spec[2:]
        #         elif required_spec.startswith(">"):
        #             return installed_ver > required_spec[1:]
        #         elif required_spec.startswith("<"):
        #             return installed_ver < required_spec[1:]
        #         elif required_spec.startswith("~="):
        #             # 兼容版本：近似等于
        #             base_version = parse(required_spec[2:])
        #             installed_version = parse(installed_ver)
        #             return (installed_version >= base_version and
        #                     installed_version < base_version.next_minor())
        #     except:
        #         return False
        #
        # return True

    def find_matching_env(self, requirements: List[str], python_version: str = None) -> Optional[str]:
        """
        查找满足requirements的现有环境

        Args:
            requirements: 包要求列表
            python_version: 可选的Python版本要求

        Returns:
            匹配的环境名称，或None
        """
        if not requirements:
            # 如果没有要求，返回第一个环境
            return next(iter(self.env_metadata.keys()), None)

        print(f"[EM] Looking for environment matching {len(requirements)} requirements...")

        for env_name, metadata in self.env_metadata.items():
            # 检查Python版本
            if python_version and metadata.get("python_version") != python_version:
                continue

            env_packages = metadata.get("packages", {})
            all_satisfied = True

            for req in requirements:
                if not req or req.startswith("#"):
                    continue

                pkg_name, version_spec = self._parse_requirement(req)
                if not pkg_name:
                    continue

                # 检查包是否安装
                if pkg_name not in env_packages:
                    all_satisfied = False
                    break

                # 检查版本是否满足
                installed_ver = env_packages[pkg_name]
                if not self._check_package_compatibility(installed_ver, version_spec):
                    all_satisfied = False
                    break

            if all_satisfied:
                print(f"[EM] Found matching environment: {env_name}")
                return env_name

        print("[EM] No matching environment found")
        return None

    def create_new_env(self, env_name: str, python_version: str = "3.12") -> str:
        """创建新的虚拟环境"""
        env_path = self.get_venv_path_by_name(env_name)
        python_exe = self.get_python_executable(env_path)

        if not os.path.exists(python_exe):
            print(f"[UV] Creating new VENV: {env_name}...")
            try:
                subprocess.check_call(
                    [self.uv_bin, "venv", env_path, "--python", python_version],
                    stdout=subprocess.DEVNULL
                )
                print(f"[UV] Venv created with Python {python_version}")
            except subprocess.CalledProcessError as e:
                print(f"[UV] Venv creation failed: {e}")
                raise

        return env_path

    def ensure_env(self, workflow_id: str, version: str, requirements: List[str], use_venv: bool) -> str:
        """
        使用 uv 准备环境
        先尝试匹配现有环境，如无匹配则创建新环境
        """
        if not use_venv:
            return sys.executable

        # 1. 首先尝试查找匹配的现有环境
        env_name = self.find_matching_env(requirements)

        if env_name:
            # 使用现有环境
            env_path = self.get_venv_path_by_name(env_name)
            python_exe = self.get_python_executable(env_path)

            print(f"[EM] Reusing existing environment: {env_name}")

            # 检查是否安装了alayaflow
            alayaflow_marker = os.path.join(env_path, "alayaflow_installed.marker")
            if not os.path.exists(alayaflow_marker):
                self._install_alayaflow(env_path)

            return python_exe

        # 2. 没有匹配的环境，创建新环境
        import uuid
        # todo: use function create_new_env instead?
        # env_name = f"{workflow_id}_{version}"
        # env_path = self.get_venv_path(workflow_id, version)
        env_name=uuid.uuid4().hex
        env_path = self.get_venv_path_by_name(env_name)
        python_exe = self.get_python_executable(env_path)

        # 1. 创建虚拟环境 (uv venv)
        # 优势：如果指定了 python 版本，uv 会自动下载便携版，不需要系统预装
        if not os.path.exists(python_exe):
            print(f"[UV] Creating VENV for {workflow_id} v{version}...")
            try:
                # 假设我们想让这个环境使用 Python 3.12 (即使主程序是 3.9)
                # 实际中你可以把 python_version 放在元数据里传进来
                target_python = "3.12"

                subprocess.check_call(
                    [self.uv_bin, "venv", env_path, "--python", target_python],
                    stdout=subprocess.DEVNULL
                )
                print(f"[UV] Venv created with Python {target_python}")
            except subprocess.CalledProcessError as e:
                print(f"[UV] Venv creation failed: {e}")
                raise

        # 2. 安装依赖 (uv pip install)
        if requirements:
            self._install_requirements(env_path, requirements)

        # 安装 alayaflow
        self._install_alayaflow(env_path)

        # 更新环境元数据
        self._update_env_packages(env_name)

        return python_exe

    def _install_requirements(self, env_path: str, requirements: List[str]):
        """安装依赖"""
        marker_file = os.path.join(env_path, "deps_installed.marker")
        if not os.path.exists(marker_file):
            print(f"[UV] Installing deps: {requirements}")
            try:
                cmd = [self.uv_bin, "pip", "install", "-p", env_path] + requirements

                subprocess.check_call(
                    cmd,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.PIPE
                )
                with open(marker_file, "w") as f:
                    f.write("ok")
                print("[UV] Deps installed.")

            except subprocess.CalledProcessError as e:
                print(f"[UV] Install failed: {e}")
                raise

    def _install_alayaflow(self, env_path: str):
        """安装alayaflow"""
        # todo: Why this appear？
        alayaflow_marker = os.path.join(env_path, "alayaflow_installed.marker")
        if not os.path.exists(alayaflow_marker):
            print("[UV] Installing alayaflow in worker env...")
            try:
                if settings.is_dev():
                    # dev模式：可编辑安装
                    subprocess.check_call(
                        [self.uv_bin, "pip", "install", "-p", env_path, "-e", str(settings.alayaflow_root)]
                    )
                elif settings.is_uneditable_dev():
                    # dev-uneditable：从项目根目录安装（非可编辑模式）
                    print(f"[UV] Installing alayaflow from: {settings.alayaflow_root}")
                    subprocess.check_call(
                        [self.uv_bin, "pip", "install", "-p", env_path, str(settings.alayaflow_root)],
                        stdout=subprocess.DEVNULL,
                        stderr=subprocess.PIPE,
                    )
                elif settings.is_prod():
                    # prod模式：从PyPI安装
                    subprocess.check_call(
                        [self.uv_bin, "pip", "install", "-p", env_path, "alayaflow"],
                        stdout=subprocess.DEVNULL,
                        stderr=subprocess.PIPE
                    )
                else:
                    raise ValueError(f"Unknown dev_mode: {settings.dev_mode}")
                with open(alayaflow_marker, "w") as f:
                    f.write("ok")
                print("[UV] Alayaflow installed.")
            except subprocess.CalledProcessError as e:
                print(f"[UV] Alayaflow installation failed: {e}")
                raise

    def list_environments(self) -> List[Dict]:
        """列出所有环境及其信息"""
        envs = []
        for env_name, metadata in self.env_metadata.items():
            env_info = {
                "name": env_name,
                "path": metadata.get("path", ""),
                "python_version": metadata.get("python_version", "unknown"),
                "package_count": len(metadata.get("packages", {})),
            }
            envs.append(env_info)
        return envs

    # def clean_orphaned_envs(self):
    #     # 不想开放这个功能
    #     """清理元数据中不存在的环境"""
    #     env_dirs = os.listdir(self.base_dir)
    #
    #     # 找出元数据中有但实际不存在的环境
    #     to_remove = []
    #     for env_name in list(self.env_metadata.keys()):
    #         env_path = self.get_venv_path_by_name(env_name)
    #         if not os.path.exists(env_path):
    #             to_remove.append(env_name)
    #
    #     # 清理元数据
    #     for env_name in to_remove:
    #         # del self.env_metadata[env_name]
    #         print(f"[UV] Remove orphaned metadata for {env_name}")
    #
    #     if to_remove:
    #         self._save_metadata()

    def get_env_info(self, env_name: str) -> Optional[Dict]:
        """获取环境的详细信息"""
        if env_name in self.env_metadata:
            return self.env_metadata[env_name]
        return None