#!/bin/python3
import sys

from bottles.backend.managers.installer import InstallerConfig

pkgdatadir="/opt/apps/deepin-wine-builder/files/share/deepin-wine-builder"
if pkgdatadir.startswith('/'):
    sys.path.insert(1, pkgdatadir + '/../../lib/python3/dist-packages')
    sys.path.insert(1, pkgdatadir)

import os
import argparse
import shutil
from pathlib import Path
from typing import Optional, List

from deepin.installer.private import ProgramsPrivate

from bottles.backend.logger import Logger
from bottles.backend.models.result import Result
from bottles.backend.utils.singleton import Singleton
from bottles.backend.globals import Paths, ConfigPaths
from bottles.backend.logger import FileLogger, JournalSeverity
from deepin.package.config import PackageConfig, DebReleaseType
from deepin.runner.runner import AppRunner
from deepin.runner.status_pipefifo import StatusPipeFifoNotify

from deepin.runner.lnk_parser import get_programs
import json

APP_VERSION = "3.5.0.4"
logging = Logger()

class ProgramsInstaller(ProgramsPrivate, metaclass=Singleton):
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

        self._init_args()
        self._process_args()

    def _init_args(self):
        self.parser = argparse.ArgumentParser(description='Deepin Wine Bottles.')
        self.parser.add_argument("-v", "--version", action="version", version=f"Bottles {APP_VERSION}")
        self.parser.add_argument("-c", "--config", help="configure search path for registry and predll", required=False, type=str)
        self.parser.add_argument("--pipelog", help="file path to create fifo pipe that record logs", required=False, type=str)

        subparsers = self.parser.add_subparsers(dest='command', help='sub-command help')

        list_parser = subparsers.add_parser("list", help="show all config info")
        list_parser.add_argument("type", choices=['dependency', 'program'], help="type of info")

        install_parser = subparsers.add_parser("install", help="install program or dependency")
        install_parser.add_argument("type", choices=['dependency', 'program'], help="type of install info")
        install_parser.add_argument("--name", help="name of install program or dependency", type=str)
        install_parser.add_argument("path", help="bottle path")
        install_parser.add_argument("--yml", help="path of yml config to install program", type=str)
        install_parser.add_argument("--notifypipe", type=str, required=False)

        show_entries_parser = subparsers.add_parser("entries", help="show entries in bottles")
        show_entries_parser.add_argument("path", help="bottle path")
        show_entries_parser.add_argument("runner", help="wine runner path")

        make_traget_parser = subparsers.add_parser("make", help="make bottle traget files")
        make_traget_parser.add_argument("type", choices=['bottle', 'deb'], help="type of traget info")
        make_traget_parser.add_argument("name", help="name of program bottle, or yml file to build deb package")
        make_traget_parser.add_argument("-c", "--deb", help="path of deb file to compare")
        make_traget_parser.add_argument("--yml", help="make yml config deb package", action="store_true")
        make_traget_parser.add_argument("--index", help="application index name to package predll and specified files", type=str)
        make_traget_parser.add_argument("-s", "--skip-installer", help="path of yml config to install program", action="store_true")
        make_traget_parser.add_argument("--entries", help="optional entries to package", nargs="*")
        make_traget_parser.add_argument(
            "--bottles-path",
            help="bottle path to generate desktop files",
            type=str,
            required=False
        )
        make_traget_parser.add_argument("--save-path", help="path to save deb package", type=str, required=False)

        patch_parser = subparsers.add_parser("patch", help="show commit id of wine patch")
        patch_parser.add_argument("name", help="app name")

        patch_parser = subparsers.add_parser("runner", help="show wine runner")
        patch_parser.add_argument("name", help="app name")

        patch_parser = subparsers.add_parser("bottle", help="show bottle info")
        patch_parser.add_argument("name", help="app name")

        run_parser = subparsers.add_parser("run", help="run wine applicaion from yml config file")
        run_parser.add_argument("yml_path", help="yml path for wine application")
        run_parser.add_argument("--entry", help="the entry exe name to launch", metavar="EXE_NAME", required=False, type=str)
        run_parser.add_argument("--args", dest="run_args", nargs=argparse.REMAINDER, help="arguments pass to wine application")

    def _process_args(self):
        FileLogger.write(JournalSeverity.INFO, f"Start bottles: {sys.argv}")
        self.args = self.parser.parse_args()

        if self.args.config:
            if not ConfigPaths().load_config(self.args.config):
                logging.error(f"Failed to load config {self.args.config}")

        if self.args.pipelog:
            FileLogger.set_pipelogpath(self.args.pipelog)

        self._init_installer()

        if self.args.command == "list":
            if self.args.type == "dependency":
                self.list_all_dependencies()
            elif self.args.type == "program":
                self.list_all_programs()
        elif self.args.command == "install":
            # 转换成绝对路径
            bottle_path = os.path.realpath(self.args.path)

            if self.args.type == "dependency":
                if self.args.name:
                    self.install_dependency(self.args.name, bottle_path)
            elif self.args.type == "program":
                if self.args.name:
                    self.install_program(bottle_path, name=self.args.name)
                elif self.args.yml:
                    self.install_program(bottle_path, yml_path=self.args.yml)

        elif self.args.command == "make":
            self.build_app_target(self.args.type, self.args.name, self.args.deb, self.args.yml, self.args.index)
        elif self.args.command == "patch":
            self.show_app_patch(self.args.name)
        elif self.args.command == "runner":
            self.show_app_runner(self.args.name)
        elif self.args.command == "bottle":
            self.show_bottle_info(self.args.name)
        elif self.args.command == "run":
            self.run_application(self.args.yml_path, self.args.entry, self.args.run_args)
        elif self.args.command == "entries":
            self.show_bottle_entries()
        else:
            self.parser.print_help()

    def list_all_dependencies(self):
        all_deps = self.dependency_manager.fetch_catalog()
        logging.info(f"support dependencis: {list(all_deps.keys())}")

    def install_dependency(self, dependency, bottle_path) -> Result:
        logging.info(f"install dependency: {dependency}")
        self.config.Name = dependency
        self.config.Path = bottle_path
        if os.path.exists(self.config.installer_yml()):
            mainifest = self.installer_manager.load_manifest(yml_path=self.config.installer_yml())
            deb_info = mainifest.get("DebInfo") or {}
            wine_cmd = deb_info.get("wine_cmd")
            if wine_cmd:
                self.config.Runner = wine_cmd

        return self.dependency_manager.install(self.config, dependency)

    def list_all_programs(self) -> dict:
        all_programs = self.installer_manager.fetch_catalog()
        logging.info(f"support programs: {list(all_programs.keys())}")

    def install_program(self, bottle_path, name: Optional[str] = None, yml_path: Optional[str] = None):
        self.config.Path = bottle_path
        if name:
            logging.info(f"install program: {name}")
            return self.installer_manager.install_program(self.config, InstallerConfig.DebPackageConfig(), installer=name)
        elif yml_path:
            runner = AppRunner(yml_path)

            if (self.args.notifypipe):
                self.config.notify_fn = StatusPipeFifoNotify(self.args.notifypipe)
                self.config.notify_fn.start()

            logging.info(f"install program: {yml_path}")
            res = runner.install_program(self.installer_manager, self.config)
            if not res.ok:
                sys.exit(1)
            self.config.notify_fn.stop()

    def show_bottle_info(self, name: str):
        manifest = self.installer_manager.get_installer(name)
        bottle_checksum = self.installer_manager.get_bottle_checksum(manifest)
        logging.info(f"{name} bottle checksum: {bottle_checksum}")

    def show_app_runner(self, name: str):
        manifest = self.installer_manager.get_installer(name)
        debinfo = manifest.get("DebInfo")
        if debinfo:
            runner = debinfo.get("wine_cmd")
            if runner:
                logging.info(f"{name} runner: {runner}")

    def show_app_patch(self, name: str):
        manifest = self.installer_manager.get_installer(name)
        debinfo = manifest.get("DebInfo")
        if debinfo:
            patch_list = debinfo.get("predll_patch")
            if patch_list:
                for patch in patch_list:
                    patch_id = patch.get("patch_id")
                    patch_arch = patch.get("patch_arch")
                    patch_url = patch.get("patch_url")
                    patch_checksum = patch.get("patch_checksum")
                    if patch_id and patch_arch and patch_url and patch_checksum:
                        logging.info(f"{name} patch id: {patch_id}")

    def build_app_target(self, type: str, name:str, deb_file: str, yml_deb: bool, app_index_name: Optional[str]):
        using_yml_path = False
        if (os.path.exists(name)):
            # 如果传进的是 yml 文件
            self.config.Name = Path(name).name
            using_yml_path = True
            name = os.path.abspath(name)
        else:
            self.config.Name = name

        review_log = os.environ.get("review_log")
        if not review_log:
            review_log = f"/tmp/{self.config.Name}.diff"
            logging.info(f"output review log to : {review_log}")
            fp = open(review_log, 'w')
            fp.close()
            os.environ["review_log"] = review_log


        self.config.Path = os.path.join(Paths.deepinwine_temp, "bottle")
        if os.path.exists(self.config.Path):
            shutil.rmtree(self.config.Path)

        if type == "bottle":
            res = Result(False)
            if using_yml_path:
                res = self.installer_manager.install_yaml(self.config, InstallerConfig.BaseBottleConfig(), yml_path=name)
            else:
                res = self.installer_manager.install_program(self.config, InstallerConfig.BaseBottleConfig(), installer=name)
            if not res.status:
                logging.error(f"install failed: {res.message}")
                return

            self.clean_bottle_tmp()
            self.print_bottle_diff()
            bottle_file = self.compress_bottle()
            if bottle_file:
                self.compare_bottle_size(name, bottle_file)

        elif type == "deb":
            packageinfo = PackageConfig()
            res = Result(False)

            if using_yml_path:
                packageinfo = self.installer_manager.get_package_config(yml_path=name)
            else:
                packageinfo = self.installer_manager.get_package_config(installer=name)

            if app_index_name:
                packageinfo.app_index_name = app_index_name
            elif using_yml_path:
                _name = self.installer_manager.load_manifest(yml_path=name).get("Name")
                packageinfo.app_index_name = str(_name)
            else:
                packageinfo.app_index_name = name

            if self.args.save_path is not None:
                if Path(self.args.save_path).is_dir():
                    packageinfo.deb_save_dir = self.args.save_path
                else:
                    logging.error(f"{self.args.save_path} 不是有效的保存目录")
                    sys.exit(1)

            # 使用 AppRunner 收集 desktop 信息
            # FIXME: 在 AppRunner 支持读取 repo 中的 yml 文件
            runner = AppRunner(yml_path=name)

            packageinfo.deb_entries_dir=os.path.expanduser(
                f"~/.cache/deepin-wine-builder/{packageinfo.deb_package_name}_entries"
            )

            if packageinfo.wine_cmd:
                self.config.Runner = packageinfo.wine_cmd

            package_template_dir = self.package_script_dir + "/template/"
            if yml_deb:
                # 打的是 yml 的配置包，不需要打包容器
                packageinfo.deb_release_type = DebReleaseType.ymlDeb

                # 使用 AppRunner 收集 desktop 信息
                # FIXME: 在 AppRunner 支持读取 repo 中的 yml 文件
                runner = AppRunner(yml_path=name)

                if not self.args.skip_installer:
                    runner.install_program(self.installer_manager, self.config)
                    res = runner.generate_ymldeb_entries(
                        self.config,
                        outdir=packageinfo.deb_entries_dir,
                        package_template_dir=package_template_dir,
                        entries_name=self.args.entries
                    )
                else:
                    # 从已经安装的容器查找 desktop 和图标文件
                    config = self.config.copy()
                    if self.args.bottles_path:
                        config.Path = self.args.bottles_path
                    else:
                        config.Path = os.path.expanduser(f"~/.deepinwine/{packageinfo.deb_package_name}")
                    res = runner.generate_ymldeb_entries(
                        config,
                        outdir=packageinfo.deb_entries_dir,
                        package_template_dir=package_template_dir,
                        entries_name=self.args.entries
                    )

            else:
                packageinfo.deb_release_type = DebReleaseType.bottleDeb

                # 同样使用 AppRunner 来收集图标和程序入口
                runner = AppRunner(yml_path=name)

                if not self.args.skip_installer:
                    runner.install_program(self.installer_manager, self.config)
                    res = runner.generate_bottledeb_entries(
                        self.config,
                        outdir=packageinfo.deb_entries_dir,
                        package_template_dir=package_template_dir,
                        entries_name=self.args.entries
                    )
                else:
                    if self.args.bottles_path:
                        self.config.Path = self.args.bottles_path
                    else:
                        logging.error("需要指定打包的容器目录")
                        sys.exit(1)

                    res = runner.generate_bottledeb_entries(
                        self.config,
                        outdir=packageinfo.deb_entries_dir,
                        package_template_dir=package_template_dir,
                        entries_name=self.args.entries
                    )

            if not res.status:
                logging.error(f"install failed: {res.message}")
                if os.path.exists(self.config.Path):
                    shutil.rmtree(self.config.Path)
                sys.exit(1)

            if not self.package_bottle(packageinfo, self.args.skip_installer):
                logging.error("failed to running packaging script")
                sys.exit(1)

        if deb_file and os.path.isfile(deb_file):
            self.print_deb_diff(deb_file)


    def show_bottle_entries(self):
        '''显示容器内所有的应用程序'''
        self.config.Path = self.args.path
        self.config.Runner = self.args.runner
        print(json.dumps(AppRunner.list_lnks(self.config), ensure_ascii=False))

if __name__ == "__main__":
    programInstaller = ProgramsInstaller()
