# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# docker模型
# ------------------------------
import json
import os
import sys
import time

if "/www/server/panel/class" not in sys.path:
    sys.path.insert(0, "/www/server/panel/class")

os.chdir("/www/server/panel")
import public

from mod.project.docker.app.appManageMod import AppManage
# from mod.project.docker.runtime.runtimeManage import RuntimeManage
# from mod.project.docker.sites.sitesManage import SitesManage
from mod.project.docker.app.sub_app.ollamaMod import OllamaMod
from mod.project.docker.apphub.apphubManage import AppHub
from btdockerModelV2 import dk_public as dp


class main(AppManage, OllamaMod):

    def __init__(self):
        super(main, self).__init__()
        OllamaMod.__init__(self)

    # 2024/6/26 下午5:49 获取所有已部署的项目列表
    def get_project_list(self, get):
        '''
            @name 获取所有已部署的项目列表
            @author wzz <2024/6/26 下午5:49>
            @param "data":{"参数名":""} <数据类型> 参数描述
            @return dict{"status":True/False,"msg":"提示信息"}
        '''
        try:
            if self.def_name is None: self.set_def_name(get.def_name)
            if hasattr(get, '_ws') and hasattr(get._ws, 'btws_get_project_list'):
                return

            while True:
                compose_list = self.ls(get)
                if len(compose_list) == 0:
                    if hasattr(get, '_ws'):
                        get._ws.send(json.dumps(self.wsResult(
                            True,
                            data=[],
                        )))


                stacks_info = dp.sql("stacks").select()

                compose_project = []

                for j in compose_list:
                    t_status = j["Status"].split(",")
                    container_count = 0
                    for ts in t_status:
                        container_count += int(ts.split("(")[1].split(")")[0])

                    j_name = j['Name']
                    if "bt_compose_" in j_name:
                        config_path = "{}/config/name_map.json".format(public.get_panel_path())
                        name_map = json.loads(public.readFile(config_path))
                        if j_name in name_map:
                            j_name = name_map[j_name]
                        else:
                            j_name = j_name.replace("bt_compose_", "")

                    tmp = {
                        "id": None,
                        "name": j_name,
                        "status": "1",
                        "path": j['ConfigFiles'],
                        "template_id": None,
                        "time": None,
                        "remark": "",
                        "run_status": j['Status'].split("(")[0].lower(),
                        "container_count": container_count,
                    }
                    for i in stacks_info:
                        if public.md5(i['name']) in j['Name']:

                            tmp["name"] = i['name']
                            tmp["run_status"] = j['Status'].split("(")[0].lower()
                            tmp["template_id"] = i['template_id']
                            tmp["time"] = i['time']
                            tmp["remark"] = i["remark"]
                            tmp["id"] = i['id']
                            break

                        if i['name'] == j['Name']:
                            tmp["run_status"] = j['Status'].split("(")[0].lower()
                            tmp["template_id"] = i['template_id']
                            tmp["time"] = i['time']
                            tmp["remark"] = i["remark"]
                            tmp["id"] = i['id']
                            break

                    if tmp["time"] is None:
                        if os.path.exists(j['ConfigFiles']):
                            get.path = j['ConfigFiles']
                            compose_ps = self.ps(get)
                            if len(compose_ps) > 0 and "CreatedAt" in compose_ps[0]:
                                tmp["time"] = dp.convert_timezone_str_to_timestamp(compose_ps[0]['CreatedAt'])

                    compose_project.append(tmp)

                if hasattr(get, '_ws'):
                    setattr(get._ws, 'btws_get_project_list', True)
                    get._ws.send(json.dumps(self.wsResult(
                        True,
                        data=sorted(compose_project, key=lambda x: x["time"] if x["time"] is not None else float('-inf'), reverse=True),
                    )))

                time.sleep(2)
        except Exception as e:
            return public.return_message(-1, 0, str(e))

    # 2026/2/26 下午8:55 获取指定compose.yml的docker-compose ps  增加异常处理和类型检查，防止WebSocket推送过程中出现错误导致循环崩溃
    def get_project_ps(self, get):
        '''
            @name 获取指定 compose.yml 的 docker-compose ps 实时状态（WebSocket 推送）
            @author wzz <2026/2/26>
            @param get.path: compose 项目路径
            @return dict {"status": True/False, "msg": "..."}
        '''
        try:
            if self.def_name is None:
                self.set_def_name(get.def_name)

            # 确保 path 存在
            if not hasattr(get, 'path') or not get.path:
                return public.return_message(-1, 0, "Missing 'path' parameter")

            ws_flag_attr = f'btws_get_project_ps_{get.path}'

            # 防止重复订阅
            if hasattr(get, '_ws') and hasattr(get._ws, ws_flag_attr):
                result = self.wsResult(True, data=[])
                try:
                    get._ws.send(json.dumps(result))
                except:
                    pass
                return result

            from btdockerModelV2.dockerSock import container
            sk_container = container.dockerContainer()

            while True:
                try:
                    compose_list = self.ps(get)
                    # 强制确保是 list
                    if not isinstance(compose_list, list):
                        compose_list = []

                    # 发送空结果并退出（无容器）
                    if len(compose_list) == 0:
                        if hasattr(get, '_ws'):
                            try:
                                get._ws.send(json.dumps(self.wsResult(True, data=[])))
                            except:
                                pass
                        break

                    # 处理每个容器
                    for l in compose_list:
                        if not isinstance(l, dict):
                            continue

                        # 补全 Image
                        if "Image" not in l:
                            l["Image"] = ""
                            if l.get("ID"):
                                try:
                                    inspect = sk_container.get_container_inspect(l["ID"])
                                    l["Image"] = inspect.get("Config", {}).get("Image", "")
                                except:
                                    pass  # 忽略 inspect 失败

                        # 补全 Ports 字符串
                        if "Ports" not in l:
                            l["Ports"] = ""
                            publishers = l.get("Publishers")
                            if publishers and isinstance(publishers, list):
                                for p in publishers:
                                    if not isinstance(p, dict):
                                        continue
                                    url = p.get("URL", "")
                                    target = p.get("TargetPort", "")
                                    proto = p.get("Protocol", "")
                                    pub_port = p.get("PublishedPort", "")
                                    if url == "":
                                        l["Ports"] += f"{target}/{proto},"
                                    else:
                                        l["Ports"] += f"{url}:{pub_port}->{target}/{proto},"

                        # 构造结构化 ports（兼容 containerModel.struct_container_ports）
                        ports_data = {}
                        publishers = l.get("Publishers")
                        if publishers and isinstance(publishers, list):
                            for port in publishers:
                                if not isinstance(port, dict):
                                    continue
                                key = f"{port.get('TargetPort', '')}/{port.get('Protocol', '')}"
                                host_ip = port.get("URL", "")
                                host_port = str(port.get("PublishedPort", ""))
                                entry = {"HostIp": host_ip, "HostPort": host_port}
                                if key not in ports_data:
                                    ports_data[key] = [entry] if host_ip else None
                                elif ports_data[key] is not None:
                                    ports_data[key].append(entry)
                        l["ports"] = ports_data

                    # 推送数据
                    if hasattr(get, '_ws'):
                        setattr(get._ws, ws_flag_attr, True)
                        try:
                            get._ws.send(json.dumps(self.wsResult(True, data=compose_list)))
                        except:
                            # WebSocket 已断开，退出循环
                            break

                    time.sleep(2)

                except Exception:
                    # 内部循环异常，安全退出
                    break

            return self.wsResult(True, data=[])

        except Exception as e:
            return public.return_message(-1, 0, str(e))

    # 2024/11/11 14:34 获取所有正在运行的容器信息和已安装的应用信息
    def get_some_info(self, get):
        '''
            @name 获取所有正在运行的容器信息和已安装的应用信息
        '''
        get.type = get.get("type", "container")
        if not get.type in ("container", "app"):
            return public.return_message(-1, 0, public.lang("Only container and app types are supported"))

        if get.type == "container":
            from btdockerModelV2.dockerSock import container
            sk_container = container.dockerContainer()
            sk_container_list = sk_container.get_container()

            data = []
            for container in sk_container_list:
                if not "running" in container["State"]: continue

                port_list = []
                for p in container["Ports"]:
                    if not "PublicPort" in p: continue
                    if not p["PublicPort"] in port_list:
                        port_list.append(p["PublicPort"])

                data.append({
                    "id": container["Id"],
                    "name": container["Names"][0].replace("/", ""),
                    "status": container["State"],
                    "image": container["Image"],
                    "created_time": container["Created"],
                    "ports": port_list,
                })

            return public.return_message(0, 0, data)
        else:
            get.row = 10000
            installed_apps = self.get_installed_apps(get)['message']
            not_allow_category = ("Database", "System")
            if installed_apps and installed_apps.get('data', []):
                for app in installed_apps["data"]:
                    if not "running" in app["status"]:
                        installed_apps["data"].remove(app)
                    if app["apptype"] in not_allow_category:
                        installed_apps["data"].remove(app) if app in installed_apps["data"] else None

            # return public.returnResult(status=installed_apps["status"], data=installed_apps["data"])
            return public.return_message(0, 0, installed_apps)

    def generate_apphub(self, get):
        '''
            @name 解析外部应用列表
            @author csj <2025/7/9>
            @return dict{"status":True/False,"msg":"提示信息"}
        '''
        return AppHub().generate_apphub(get)

    def create_app(self,get):
        '''
            @name 创建应用
            @author csj <2025/7/9>
            @return dict{"status":True/False,"msg":"提示信息"}
        '''
        if get.get("appid","0") == "-1": # 从apphub创建应用
            self.templates_path = os.path.join(AppHub.hub_home_path, "templates")
            self.apps_json_file = os.path.join(AppHub.hub_home_path, "apps.json")

            version = get.get("version","latest")
            app_name = get.get("app_name","")

            if not os.path.exists(self.templates_path):
                os.makedirs(self.templates_path)

            #/www/dk_project/dk_app/apphub/apphub/templates/app_name/version
            app_version_path = os.path.join(AppHub.hub_home_path, app_name, version)
            if not os.path.exists(app_version_path):
                return public.return_message(-1, 0, public.lang("Version {} for applying {} does not exist", (version, app_name)))

            # /www/dk_project/dk_app/apphub/apphub/templates/app_name
            app_template_path = os.path.join(self.templates_path, app_name)

            public.ExecShell("\cp -r {} {}".format(app_version_path,app_template_path))

        return super().create_app(get)

    def get_apphub_config(self, get):
        '''
            @name 获取apphub配置
            @author csj <2025/7/9>
            @return dict{"status":True/False,"data":{}}
        '''
        return public.return_message(0, 0, AppHub.get_config())

    def set_apphub_git(self,get):
        '''
            @name 设置外部应用的git地址
            @author csj <2025/7/9>
            @param get: git_url, git_branch, user, password
        '''
        if not hasattr(get, 'git_url') or not get.git_url:
            return public.return_message(-1, 0, public.lang("GIT ADDRESS IS NOT SET"))
        if not hasattr(get, 'git_branch') or not get.git_branch:
            return public.return_message(-1, 0, public.lang("The branch name is not set"))

        return AppHub().set_apphub_git(get)

    def import_git_apphub(self,get):
        '''
            @name 从git导入外部应用
            @author csj <2025/7/9>
        '''
        return AppHub().import_git_apphub(get)

    def install_apphub(self,get):
        '''
            @name 安装apphub所需环境
            @author csj <2025/7/9>
        '''
        return AppHub().install_apphub(get)

    def import_zip_apphub(self,get):
        '''
            @name 从zip包导入外部应用
            @author csj <2025/7/9>
            @param get: sfile: zip文件路径
        '''
        if not hasattr(get, 'sfile') or not get.sfile:
            return public.return_message(-1, 0, public.lang("The zip file path is not set"))

        return AppHub().import_zip_apphub(get)

    def parser_zip_apphub(self,get):
        '''
            @name 解析zip包
            @author csj <2025/7/9>
            @param get: sfile: zip文件路径
            @return dict{"status":True/False,"data":[]}
        '''
        if not hasattr(get, 'sfile') or not get.sfile:
            return public.return_message(-1, 0, public.lang("Please select the file path"))

        app_list = []
        files = get.sfile.split(',')
        for sfile in files:
            get.sfile = sfile

            apps = AppHub().parser_zip_apphub(get)
            app_list.extend(apps)
        public.print_log(app_list)
        return public.return_message(0, 0, app_list)



if __name__ == '__main__':
    pass
