import ast
import importlib.metadata
from pathlib import Path
from typing import Set, List, Optional

from leaf_publisher.data.packages import get_stdlib_modules
from leaf_publisher.cache import load_deps_cache, save_deps_cache

# 忽略的目录（扫描时跳过）
IGNORE_DIRS = {'.git', '__pycache__', 'venv', 'env', '.venv', 'dist', 'build', '*.egg-info'}


def extract_top_level_imports(file_path: Path) -> Set[str]:
    """使用 AST 解析单个 .py 文件，返回所有顶级第三方包名"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            tree = ast.parse(f.read(), filename=str(file_path))
    except (SyntaxError, UnicodeDecodeError):
        return set()

    stdlib = get_stdlib_modules()
    imports = set()

    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                top = alias.name.split('.')[0]
                if top not in stdlib:
                    imports.add(top)
        elif isinstance(node, ast.ImportFrom):
            if node.module and not node.level:  # 忽略相对导入
                top = node.module.split('.')[0]
                if top not in stdlib:
                    imports.add(top)
    return imports


def scan_project_imports(project_dir: Path) -> Set[str]:
    """递归扫描项目目录，收集所有第三方依赖包名"""
    imported = set()
    for py_file in project_dir.rglob('*.py'):
        if any(part in IGNORE_DIRS for part in py_file.parts):
            continue
        imported.update(extract_top_level_imports(py_file))
    return imported


def get_installed_version(package_name: str) -> Optional[str]:
    """获取当前环境中已安装包的版本，若未安装返回 None"""
    try:
        return importlib.metadata.version(package_name)
    except importlib.metadata.PackageNotFoundError:
        return None


def parse_pipfile(project_dir: Path) -> List[str]:
    """解析 Pipfile，返回依赖列表（格式如 'package==version'）"""
    pipfile_path = project_dir / 'Pipfile'
    if not pipfile_path.exists():
        return []
    try:
        import tomllib  # Python 3.11+
    except ImportError:
        try:
            import tomli as tomllib
        except ImportError:
            print("⚠️ 请安装 tomli 以解析 Pipfile")
            return []
    with open(pipfile_path, 'rb') as f:
        data = tomllib.load(f)
    packages = data.get('packages', {})
    deps = []
    for pkg, ver in packages.items():
        if isinstance(ver, str):
            if ver == '*':
                deps.append(pkg)
            else:
                if ver and ver[0] in '~=<>!':
                    deps.append(f"{pkg}{ver}")
                else:
                    deps.append(f"{pkg}=={ver}")
        elif isinstance(ver, dict):
            ver_str = ver.get('version', '*')
            if ver_str == '*':
                deps.append(pkg)
            else:
                if ver_str and ver_str[0] in '~=<>!':
                    deps.append(f"{pkg}{ver_str}")
                else:
                    deps.append(f"{pkg}=={ver_str}")
        else:
            deps.append(pkg)
    return deps


def parse_poetry_lock(project_dir: Path) -> List[str]:
    """解析 poetry.lock，返回依赖列表（格式 'package==version'）"""
    lock_path = project_dir / 'poetry.lock'
    if not lock_path.exists():
        return []
    try:
        import tomllib
    except ImportError:
        try:
            import tomli as tomllib
        except ImportError:
            print("⚠️ 请安装 tomli 以解析 poetry.lock")
            return []
    with open(lock_path, 'rb') as f:
        data = tomllib.load(f)
    packages = data.get('package', [])
    deps = []
    for pkg in packages:
        name = pkg.get('name')
        version = pkg.get('version')
        if name and version:
            deps.append(f"{name}=={version}")
    return deps


def generate_dependencies(project_dir: Path,
                          version_policy: str = 'minimum',
                          use_cache: bool = True,
                          source: str = 'auto') -> List[str]:
    """
    获取依赖列表。
    source: 'auto', 'scan', 'pipfile', 'poetry'
    """
    if source == 'auto':
        # 优先使用 poetry.lock，其次 Pipfile，最后扫描
        if (project_dir / 'poetry.lock').exists():
            source = 'poetry'
        elif (project_dir / 'Pipfile').exists():
            source = 'pipfile'
        else:
            source = 'scan'

    if source == 'pipfile':
        return parse_pipfile(project_dir)
    elif source == 'poetry':
        return parse_poetry_lock(project_dir)
    else:  # scan
        if use_cache:
            cached = load_deps_cache(project_dir, IGNORE_DIRS)
            if cached is not None:
                return cached
        candidates = scan_project_imports(project_dir)
        deps = []
        for pkg in sorted(candidates):
            ver = get_installed_version(pkg)
            if ver:
                if version_policy == 'exact':
                    spec = f'{pkg}=={ver}'
                elif version_policy == 'compatible':
                    parts = ver.split('.')
                    if len(parts) >= 2:
                        compat_ver = '.'.join(parts[:2])
                    else:
                        compat_ver = ver
                    spec = f'{pkg}~={compat_ver}'
                else:
                    spec = f'{pkg}>={ver}'
                deps.append(spec)
        if use_cache:
            save_deps_cache(project_dir, deps, IGNORE_DIRS)
        return deps