#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
-------------------------------------------------------------------------
This file is part of the MindStudio project.
Copyright (c) 2025 Huawei Technologies Co.,Ltd.

MindStudio is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:

         http://license.coscl.org.cn/MulanPSL2

THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
-------------------------------------------------------------------------
"""

import logging
import os
import shutil
import subprocess  # nosec B404
from configparser import ConfigParser
from datetime import datetime

from setuptools import setup, find_packages
from setuptools.command.build_py import build_py as _build_py
from setuptools.command.develop import develop as _develop
from setuptools.command.sdist import sdist as _sdist


def _ensure_data_symlinks():
    """pip -e 安装时为数据目录创建符号链接, 保持单一数据源。"""
    pkg_dir = os.path.dirname(os.path.abspath(__file__))
    ms_dir = os.path.join(pkg_dir, 'msmodelslim')
    for d in ('config', 'lab_practice', 'lab_calib'):
        link = os.path.join(ms_dir, d)
        target = os.path.join(pkg_dir, d)

        # 已是最新符号链接则跳过
        if os.path.islink(link) and os.readlink(link) == os.path.relpath(target, ms_dir):
            continue

        # 清理旧的符号链接或 copytree 残留目录
        if os.path.islink(link):
            os.unlink(link)
        elif os.path.exists(link):
            shutil.rmtree(link)

        # 优先创建符号链接(Windows 也尝试,可能因权限失败)
        try:
            os.symlink(os.path.relpath(target, ms_dir), link)
        except OSError:
            if os.path.exists(target):
                shutil.copytree(target, link)
                logging.warning(
                    "Failed to create symlink for '%s', using directory copy instead. "
                    "Debug by modifying files under msmodelslim/%s/, but sync changes "
                    "back to %s before committing.",
                    d,
                    d,
                    target,
                )


class _DevelopWithSymlinks(_develop):
    def run(self):
        _ensure_data_symlinks()
        _develop.run(self)


def _git_output(*args: str) -> str:
    repo_root = os.path.dirname(os.path.abspath(__file__))
    try:
        return (
            subprocess.check_output(  # nosec B603, B607
                ['git', *args],
                cwd=repo_root,
                stderr=subprocess.DEVNULL,
            )
            .decode()
            .strip()
        )
    except (OSError, subprocess.CalledProcessError):
        return ''


def _pack_git_branch() -> str:
    """Resolve the branch being packed so installed wheels still know their origin."""
    branch = os.environ.get('CI_COMMIT_REF_NAME') or os.environ.get('GITHUB_REF_NAME') or ''
    if not branch:
        branch = _git_output('rev-parse', '--abbrev-ref', 'HEAD')
    if branch and branch != 'HEAD':
        return branch
    named = _git_output('name-rev', '--name-only', '--exclude', 'remotes/*', 'HEAD')
    if not named or named in ('HEAD', 'undefined'):
        named = _git_output('name-rev', '--name-only', 'HEAD')
    if not named or named in ('HEAD', 'undefined'):
        return ''
    named = named.replace('remotes/origin/', '').replace('remotes/', '')
    return named.split('~', 1)[0].split('^', 1)[0]


def _write_build_info(path: str) -> None:
    """Write pack-time git hash, branch and local timestamp into the wheel/sdist."""
    git_hash = _git_output('rev-parse', 'HEAD')[:12]
    git_branch = _pack_git_branch()
    build_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, 'w', encoding='utf-8') as handle:
        handle.write(
            '# Auto-generated by setup.py at pack time. Do not edit.\n'
            f'GIT_HASH = {git_hash!r}\n'
            f'GIT_BRANCH = {git_branch!r}\n'
            f'BUILD_DATE = {build_date!r}\n'
        )


class _BuildPyWithBuildInfo(_build_py):
    def run(self):
        super().run()
        target = os.path.join(self.build_lib, 'msmodelslim', '_build_info.py')
        # sdist already baked this file; keep it when rebuilding the wheel without .git
        if not os.path.exists(target):
            _write_build_info(target)


class _SdistWithBuildInfo(_sdist):
    def make_release_tree(self, base_dir, files):
        super().make_release_tree(base_dir, files)
        _write_build_info(os.path.join(base_dir, 'msmodelslim', '_build_info.py'))


config = ConfigParser()
config.read('./config/config.ini')

abs_path = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(abs_path, "requirements.txt"), encoding='utf-8') as f:
    required = f.read().splitlines()

model_adapter_plugins = []
entry_section = config["ModelAdapterEntryPoints"]

for group, models in config.items("ModelAdapter"):
    model_list = [m.strip() for m in models.split(",")]
    if group in entry_section:
        entry_point = entry_section[group]
    else:
        logging.warning("ModelAdapter group '%s' has no entry point defined in ModelAdapterEntryPoints", group)
        continue

    for model in model_list:
        model_adapter_plugins.append(f"{model}={entry_point}")

# 从 config.ini 读取插件配置
plugin_entry_points = {}
for section_name in config.sections():
    if section_name.startswith("Plugin:"):
        # 提取插件名称(去掉 "Plugin:" 前缀)
        plugin_name = section_name[7:]  # len("Plugin:") = 7
        # 自动补全为 msmodelslim.xxx.plugins 格式
        plugin_path = f"msmodelslim.{plugin_name}.plugins"
        plugin_list = []
        for plugin_type, plugin_func_path in config.items(section_name):
            plugin_list.append(f"{plugin_type}={plugin_func_path}")
        if plugin_list:
            plugin_entry_points[plugin_path] = plugin_list

__version__ = '26.1.0'
whl_version = os.getenv('WHL_VERSION')
if whl_version is not None:
    __version__ = whl_version

setup(
    name='msmodelslim',
    version=__version__,
    description='msModelSlim, MindStudio ModelSlim Tools',
    long_description_content_type='text/markdown',
    url=config.get('URL', 'repository_url'),
    cmdclass={
        'build_py': _BuildPyWithBuildInfo,
        'sdist': _SdistWithBuildInfo,
        'develop': _DevelopWithSymlinks,
    },
    packages=find_packages(
        exclude=[
            'precision_tool',
            'security',
        ]
    )
    + ['msmodelslim.config', 'msmodelslim.lab_calib', 'msmodelslim.lab_practice'],
    package_dir={
        'msmodelslim': 'msmodelslim',
        'msmodelslim.config': 'config',
        'msmodelslim.lab_calib': 'lab_calib',
        'msmodelslim.lab_practice': 'lab_practice',
    },
    package_data={
        '': [
            'LICENSE',
            'data.json',
            'README.md',
            '*.txt',
            '*.bat',
            '*.sh',
            '*.cpp',
            '*.h',
            '*.py',
            '*.so',
        ],
        'msmodelslim.config': ['*'],
        'msmodelslim.lab_calib': ['**'],
        'msmodelslim.lab_practice': ['**'],
        'msmodelslim.core.tune_strategy.common.config_builder.expert_experience': ['*.yaml', '*.yml'],
        'msmodelslim.core.analysis_service': ['pipeline_analysis/pipeline_template/*.yaml'],
    },
    data_files=[('', ['requirements.txt'])],
    license='Mulan PSL v2',
    keywords='msmodelslim',
    python_requires='>=3.8',
    install_requires=required,
    entry_points={
        'console_scripts': ['msmodelslim=msmodelslim.cli.__main__:main'],
        "msmodelslim.model_adapter.plugins": model_adapter_plugins,
        **plugin_entry_points,  # 从 config.ini 读取的插件配置(含 quant_service、tuning_strategy、evaluation 等)
    },
)