import configparser
import subprocess
import shutil
import argparse
import requests
from pathlib import Path
import time
import os
import sys
sys.path.append(str(Path(__file__).resolve().parent.parent))
from build import get_settings

def download_file(url, dest):
    """下载文件到指定路径"""
    try:
        r = requests.get(url, stream=True)
        r.raise_for_status()
        with open(dest, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
        return True
    except Exception as e:
        print(f"下载失败: {e}")
        return False

def load_config(config_path):
    """加载配置文件"""
    return get_settings()
    config = configparser.ConfigParser()
    try:
        # 指定 UTF-8 编码读取配置文件
        config.read(config_path, encoding='utf-8')
        if not config.sections():  # 检查是否成功读取到配置项
            raise ValueError(f"配置文件 {config_path} 为空或格式错误")
        return config
    except FileNotFoundError:
        raise FileNotFoundError(f"配置文件 {config_path} 不存在")
    except UnicodeDecodeError:
        # 尝试其他常见编码
        try:
            config.read(config_path, encoding='gbk')
            if not config.sections():
                raise ValueError(f"配置文件 {config_path} 为空或格式错误")
            return config
        except Exception as e:
            raise ValueError(f"配置文件 {config_path} 编码错误: {str(e)}")
    except Exception as e:
        raise ValueError(f"加载配置文件 {config_path} 失败: {str(e)}")

def build_nuitka_command(config, args, app_name):
    """构造Nuitka编译命令"""
    cmd = []
    
    # 基本选项
    
    if compiler := args.compiler or config.nuitka.compiler:
        cmd.append(f"--{compiler}")
    if args.show_progress or config.nuitka.show_progress:
        cmd.append("--show-progress")
    if args.standalone or config.nuitka.standalone:
        cmd.append("--standalone")
    if args.onefile or config.nuitka.onefile:
        cmd.append("--onefile")
    if config.nuitka.remove_output:
        cmd.append("--remove-output")
    if args.disable_console or config.nuitka.disable_console:
        cmd.append("--windows-disable-console")

    # 版本信息
    if product_version := config.version.version:
        cmd.append(f"--product-version={product_version}")
    if file_version := config.version.version_str:
        cmd.append(f"--file-version={file_version}")
    if company_name := config.version.author:
        cmd.append(f"--company-name={company_name}")
    if product_name := (config.version.alias or config.version.name):
        cmd.append(f"--product-name={product_name}")
        cmd.append(f"--output-filename={app_name}")
    
    # 添加新的编译选项
    if config.nuitka.get('follow_imports', True):
        cmd.append("--follow-imports")
    
    if lto := config.nuitka.lto:
        cmd.append(f"--lto={lto}")
    
    if jobs := args.jobs or config.nuitka.jobs:
        cmd.append(f"--jobs={jobs}")
    
    # 插件处理
    if plugins := config.nuitka.enable_plugin:
        for plugin in plugins.split(','):
            cmd.append(f"--enable-plugin={plugin.strip()}")
    
    # 包和模块包含
    if packages := config.nuitka.include_package:
        for package in packages.split(','):
            cmd.append(f"--include-package={package.strip()}")
    
    if modules := config.nuitka.include_module:
        for module in modules.split(','):
            cmd.append(f"--include-module={module.strip()}")
    
    # 不跟随的导入
    if nofollow := config.nuitka.nofollow_import_to:
        for item in nofollow.split(','):
            cmd.append(f"--nofollow-import-to={item.strip()}")
    
    # 数据文件排除
    if noinclude_data := config.nuitka.noinclude_data_files:
        for pattern in noinclude_data.split(','):
            cmd.append(f"--noinclude-data-files={pattern.strip()}")
    
    # Python标志
    if python_flags := config.nuitka.python_flag:
        for flag in python_flags.split(','):
            cmd.append(f"--python-flag={flag.strip()}")
    
    # 图标处理
    icon_source = args.icon or config.build.icon_file
    if icon_source:
        ico_path = 'favicon.ico'
        if 'ico' not in icon_source:
            from tranform_to_ico import convert_png_to_ico
            convert_png_to_ico(icon_source, ico_path, sizes=[(256, 256)], sharpen=False)
        else:
            ico_path = icon_source

        if ico_path.startswith(('http://', 'https://')):
            local_icon = Path("download.ico")
            if download_file(ico_path, local_icon):
                cmd.append(f"--windows-icon-from-ico={local_icon}")
        else:
            cmd.append(f"--windows-icon-from-ico={ico_path}")
    
    # 额外选项
    if extra_options := config.nuitka.extra_options:
        if isinstance(extra_options, str):
            extra_options = extra_options.split(',')
        cmd.extend(extra_options)
    
    # 输出目录
    output_dir = args.output or config.build.output_dir or 'dist'
    cmd.append(f"--output-dir={output_dir}")

    python_exe = sys.executable
    cmd = [python_exe, "-m", "nuitka"] + list(set(cmd))
    
    # 源文件
    source_script = args.script or config.build.source_script or 'source_script'
    cmd.append(source_script)

    return cmd

def sign_executable(config, args):
    """签名可执行文件"""
    if not args.sign:
        print("跳过签名步骤")
        return True
        
    sign_tool = config.signing.sign_tool
    if not sign_tool or not Path(sign_tool).exists():
        print("警告: signtool.exe 未找到，跳过签名")
        return False
    
    cert_file = args.cert or config.signing.cert_file
    if not cert_file or not Path(cert_file).exists():
        print("警告: 证书文件不存在，跳过签名")
        return False
    
    cert_password = args.password or config.signing.cert_password or ''
    timestamp_server = args.timestamp or config.signing.timestamp_server or 'http://timestamp.digicert.com'
    
    output_dir = args.output or config.build.output_dirt
    source_script = args.script or config.build.source_script
    exe_name = Path(source_script).stem + ".exe"
    exe_path = Path(output_dir) / exe_name
    
    sign_cmd = [
        sign_tool,
        "sign",
        "/tr", timestamp_server,
        "/td", "sha256",
        "/fd", "sha256",
        "/f", cert_file,
        "/p", cert_password,
        str(exe_path),
    ]
    
    try:
        subprocess.run(sign_cmd, check=True)
        print(f"✓ 签名成功: {exe_path}")
        
        # 验证签名
        verify_cmd = [sign_tool, "verify", "/pa", str(exe_path)]
        subprocess.run(verify_cmd, check=True)
        return True
    except subprocess.CalledProcessError as e:
        print(f"✗ 签名失败: {e}")
        return False

def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="Python打包签名工具")
    parser.add_argument('--config', default='build_config.ini', 
                       help="配置文件路径 (默认: build_config.ini)")
    parser.add_argument('--script', help="覆盖配置中的源脚本")
    parser.add_argument('--output', help="覆盖输出目录")
    parser.add_argument('--icon', help="本地或在线ICO图标路径 (http/https)")
    parser.add_argument('--jobs', help="并行进程数")
    parser.add_argument('--compiler', default="mingw64", help="编译器类型")
    parser.add_argument('--show-progress', default="store_true", help="显示编译进度")
    parser.add_argument('--standalone', action='store_true', help="强制启用standalone模式")
    parser.add_argument('--onefile', action='store_true', help="强制启用单文件模式")
    parser.add_argument('--disable-console', action='store_true', 
                       help="禁用控制台窗口 (GUI程序)")
    parser.add_argument('--no-sign', dest='sign', action='store_false',
                       help="跳过签名步骤")
    parser.add_argument('--cert', help="覆盖证书路径")
    parser.add_argument('--password', help="证书密码")
    parser.add_argument('--timestamp', help="覆盖时间戳服务器URL")
    return parser.parse_args()

def format_file_size(size_bytes):
    """将文件大小转换为易读格式"""
    for unit in ['B', 'KB', 'MB', 'GB']:
        if size_bytes < 1024.0:
            return f"{size_bytes:.2f} {unit}"
        size_bytes /= 1024.0
    return f"{size_bytes:.2f} TB"

def get_exe_path(config, args):
    """获取生成的exe文件路径"""
    output_dir = args.output or config.build.output_dirt or 'dist'
    source_script = args.script or config.build.source_script
    exe_name = Path(source_script).stem + (".exe" if os.name == 'nt' else "")
    return Path(output_dir) / exe_name

def main():
    args = parse_args()
    print("=== 开始构建 ===")
    start_time = time.time()
    build_success = True
    sign_success = False
    
    try:
        config = load_config(args.config)
    except FileNotFoundError as e:
        print(f"错误: {e}")
        return
    
    # import json; print(json.dumps(config, indent=4, ensure_ascii=False))
    # import json; print(json.dumps(get_settings(), indent=4, ensure_ascii=False))
    # return
    
    # 清理旧构建
    output_dir = args.output or config.build.output_dir or 'dist'
    if config.build.clean and Path(output_dir).exists():
        try: 
            shutil.rmtree(output_dir)
            Path(output_dir).mkdir(parents=True, exist_ok=True)
            print(f"已清理输出目录: {output_dir}")
        except Exception as e:
            print(f"清理目录时出错: {e}")
            build_success = False
    
    product_name = (config.version.alias or config.version.name)
    product_version = config.version.version
    app_name = f"{product_name}{'v'+product_version if product_version else ''}"
    print(f"应用名称: {app_name}")
    build_success = True

    if build_success:
        # 编译
        print("\n[1/3] 正在使用Nuitka编译...")
        try:
            nuitka_cmd = build_nuitka_command(config, args, app_name)
            print("执行命令:", " ".join(nuitka_cmd))
            
            compile_start = time.time()
            subprocess.run(nuitka_cmd, check=True)
            print(f"编译完成，耗时: {time.time() - compile_start:.2f}秒")
        except subprocess.CalledProcessError as e:
            print(f"编译失败: {e}")
            build_success = False
    
    if build_success and args.sign:
        # 签名
        print("\n[2/3] 正在处理签名...")
        try:
            sign_start = time.time()
            sign_success = sign_executable(config, args)
            if sign_success:
                print(f"签名完成，耗时: {time.time() - sign_start:.2f}秒")
        except Exception as e:
            print(f"签名过程中出错: {e}")
            sign_success = False

    if build_success:
        # 压缩文件
        print("\n[3/3] 正在压缩文件...")
        import zipfile
        from datetime import datetime
        zip_path = f"{output_dir}/{app_name}.zip"
        try:
            with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for file in [f'{output_dir}/{app_name}.exe', *(config.build.resource_files or [])]:
                    if not file: continue
                    filename = Path(file).name
                    zipf.write(file, arcname=filename)
            print(f"压缩完成: {zip_path}")
        except Exception as e:
            print(f"压缩文件时出错: {e}")
            build_success = False
        
        zip_app_name = f"{config.version.name}{'v'+product_version if product_version else ''}.zip"
        version_data = config.version.to_dict()
        save_url = config.build.save_url.strip('/')
        access_url = config.version.access_url.strip('/')
        version_data['url'] = access_url + '/' + zip_app_name
        
        import json
        with open(f'{output_dir}/version.json', 'w', encoding='utf-8') as f:
            json.dump(dict(
                data=version_data,
                code=200,
                success='true',
                message=f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - {app_name} 打包完成',
            ), f, ensure_ascii=False, indent=4)
        print(f"版本信息已保存到: {output_dir}/version.json")
        
        if not os.system(f'curl -T {zip_path} {save_url}/{zip_app_name}'):
            print(f"上传完成: {version_data['url']}")
            os.system(f'curl -T {output_dir}/version.json {save_url}/latest')

    # 构建总结
    print("\n=== 构建总结 ===")
    total_time = time.time() - start_time
    status = "成功" if build_success else "失败"
    
    summary = [
        f"构建状态: {status}",
        f"总耗时: {total_time:.2f}秒",
        f"输出目录: {output_dir}",
    ]
    
    if build_success:
        exe_path = get_exe_path(config, args)
        if exe_path.exists():
            file_time = time.strftime('%Y-%m-%d %H:%M:%S', 
                                time.localtime(exe_path.stat().st_mtime))
            summary.extend([
                f"生成文件: {exe_path}",
                f"文件大小: {format_file_size(exe_path.stat().st_size)}",
                f"修改时间: {file_time}",
            ])

        if args.sign:
            summary.append(f"签名状态: {'成功' if sign_success else '失败'}")
    
    print("\n".join(summary))
    
    if not build_success:
        sys.exit(1)

if __name__ == "__main__":
    main()