#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from libs.inc import CliTool
import os, json, sys
from conf.app_const import *

##校验前后分销项目是否合法
def verify_febe_app_legal(version_file, pub_app):
    # 检查文件是否存在, 不存在默认允许
    if os.path.exists(version_file) is False:
        return True

    app_mode = None
    with open(version_file, 'r') as file:
        # 解析 JSON 数据
        data = json.load(file)
        # 获取appName的值
        app_mode = data.get('appMode', None)
    
    if app_mode is None:
        return True

    app_name_list = {
        'earth': ['earth'],
        'desktop': ['earth-desktop'],
        'dianjiufang': ['earth-dianjiufang'],
        'easyboss': ['dm-erp'],
        'greatboss': ['greatboss'],
        'dz': ['fetch-bank-v2-dz', 'honor-ksdz-v2', 'honor-xhs-dz', 'honor-xhs-dz-order-v2', 'honor-sphxd-dz'],
        'tbk': ['fetch-bank-v2-tbk', 'honor-xhs-tbk', 'honor-xhs-tbk-order-v2'],
        'ry': ['fetch-bank-v2-ry', 'honor-xhs-ry', ' honor-xhs-ry-order-v2'],
    }
    real_app_mode = next((key for key, value in app_name_list.items() if pub_app in value), None)

    if real_app_mode is None:
        return True
    
    if real_app_mode !=  app_mode:
        print("发布的app {} 和编译生产的appMode {} 不一致, 中断发布".format(pub_app, app_mode))
        return False
    
#前后端分离项目，只发布sw机器，公用后端
def is_ay_febe_app(idc, app):
    if idc == 'aliyun':
        return app in [
            'honor-ksdz-v2', 
            'fetch-bank-v2', 
            'fetch-bank-v2-ms', 
            'fetch-bank-v2-ds', 
            'fetch-bank-v2-dz', 
            'fetch-bank-v2-gold', 
            'fetch-bank-v2-ry', 
            'fetch-bank-v2-tbk', 
            'fetch-bank-v2-ds', 
            'fetch-bank-v2-dmds', 
            'cb-bmp',
            'honor-xhs-dz', 
            'honor-xhs-ry', 
            'honor-xhs-tbk',
            'honor-xhs-dz-order-v2',
            'honor-xhs-tbk-order-v2',
            'honor-xhs-ry-order-v2',
            'honor-sphxd-tbk',
            'honor-sphxd-order-tbk',
            'honor-sphxd-order-ry',
            'honor-ksdz-order-v2',
            'earth-h5-web', 
            'easyboss-h5-web', 
            'honor-ap',
            'honor-dd-ir',
            'honor-dddz-ir',
            'honor-shop-desktop',
            'honor-shop-desktop-dz',
            'honor-shop-desktop-tbk'
        ]
    
    if idc == 'ddms':
        return app in [
            'honor-dd-ir'
        ]
    
    if idc == 'dddz':
        return app in [
            'honor-dddz-ir'
        ]

##检查app部署类型，返回febe和classic
def get_app_deploy_type(code_path):
    for directory in ['api', 'web']:
        directory_path = os.path.join(code_path, directory)
        if not os.path.exists(directory_path):
            print("目录 {} 不存在，返回 classic ".format(directory))
            return 'classic'
    
    return 'febe'

def get_pubish_module(app, env, idc=False):
    if env == 'pre':
        if app in [
            'greatboss', 'dm-erp', 'gwms',
            'joy-tbk', 'joy-order-tbk',
            'proxy-center', 'order-print',
            'honor-1688-ds-api', 'honor-1688-dm-ds', 'honor-1688-ds-tbk-api', 'honor-dd-order'
        ]:
            return 'www/{}-pre'.format(app)

    if app in ['tbk-crm']:
        return 'www/coupon-pre' if env == 'pre' else 'www/coupon'
    if app in ['tbk']:
        return 'www/tbdiscount-pre' if env == 'pre' else 'www/tbdiscount'
    
    if idc == 'vpc8':
        return 'www/{}'.format(app)
    
    return 'www/{}'.format(app)

def is_not_skip_atjaxm_app(app):
    ## 这些app暂时没有joa-开始的玉米
    return app in [
        'joy-zndz-ai',
        'jddzmicro-ai',
        'jdmicro-ai',
        'joy-ai-flow',
        'joy-kc',
        'jiancent-finance',

        'aone',
        'dwh',
        'publish',
        'jcrm',
        '91miaoshou'
    ]

def is_dist_only(app):
    return app in [
        'easyboss-h5',
        'greatboss-h5',
        'ykt-ocr',
    ]
    
##获取代码的代码路径
def get_publish_code_path(app, is_intra = False):
    if is_intra is True:
        return '/alidata/www/{}/'.format(app)
    
    base_dir = CliTool.workspace()
    default_path = '{}.git'.format(app)
    code_path = {
        'honor': 'honor.master',
        'honor-1688-dm-ds': 'honor-1688-ds-api.git',
        'honor-1688-ds-tbk-api': 'honor-1688-ds-api.git',
        'fetch-bank': 'fetch-bank-v2.git/api',
        'easyboss-h5': 'dm-erp-applet.git/build/easyboss-h5',
        'greatboss-h5': 'dm-erp-applet.git/build/greatboss-h5',
        'tbk': 'tbk-tbdiscount.git',
    }.get(app, default_path)
    
    return base_dir + code_path + '/'
    
def get_publish_ext_env(env, app):
    if env == 'pub' and app == 'fetch-bank':
        env += ',jc-pub'
        return env
    
    if app in ['earth']:
        env += ',jst-pre' if env == 'pre' else ',jst-real'

    if env != 'real':
        return env

    return env

def get_publish_zk_ext_env(env, zk):
    if zk in ['earth']:
        env += ',jst-prezk' if env == 'pre' else ',jst-realzk,joa-real'
    
    return env

##build下发布的排除文件    
def build_exclude_ext(ext_mode = None, exclude_atjmax = False):
    exclude_atjmax_exclude = ' --exclude=/api/app/libs/controller/atjmax/* --exclude=/api/app/views/page/atjmax/* '
    exclude_atjmax_exclude += ' --exclude=/app/libs/controller/atjmax/* --exclude=/app/views/page/atjmax/* '

    exclude_ext = ''
    if ext_mode == 'api':
        exclude_ext += ' --include=/api/ --exclude=/web/* '

    if ext_mode == 'web':
        exclude_ext += ' --exclude=/api/* --include=/web/dist/ '

    if exclude_atjmax is True:
        exclude_ext += exclude_atjmax_exclude

    return exclude_ext

#build发布命令
def build_command_tpl(del_type, pwd_file, exclude_ext, pubish_code_path):
    exclude_ext = CliTool.excludeFile() + exclude_ext
    cmd_tpl = 'rsync -%s --delay-updates {} --stats --port=%s --password-file={} --timeout=120 {} {} www@%s::%s/'.format(del_type, pwd_file, exclude_ext, pubish_code_path)
    return cmd_tpl

#发布sw机器的web部分
def pubish_sw(sw_hosts, code_type, mode_type, pwd_file, pubish_code_path):
    if code_type == 'api':
        return 
    
    if not sw_hosts:
        print("sw 机器为空")
        exit()
        
    exclude_ext_web =  build_exclude_ext(ext_mode='web')
    Ct = CliTool()
    cmd_tpl_web_no_del = build_command_tpl('', pwd_file, exclude_ext_web, pubish_code_path)
    print("\n\n正在发布[SW]机器 and no --del ...")
    Ct.publish(sw_hosts, mode_type, cmd_tpl_web_no_del)
    
    if code_type == 'web':
        cmd_tpl_web = build_command_tpl('--del', pwd_file, exclude_ext_web, pubish_code_path)
        print("\n\n正在发布[SW]机器")
        Ct.publish(sw_hosts, mode_type, cmd_tpl_web)
        
        
##开始发布
def start_publish(idc, app, mode, env, is_intra = False, code_type = None, is_web = False, force_port = None):
    """
    发布命令的开始入口
    
    args
    idc: str, 发布的idc机器
    app: str, 发布的app
    env : str, 发布的环境，根据host里面的hosts文件配置：real, pre, test, 等
    is_intra: bool, 是否是内网
    mode: str, 发布的模式可用为：real, rdtest, test
    is_web: bool, 是否是前后端分离项目
    code_type: str, 项目类型，可用为：api, web
    force_port: int, 强制指定端口
    """

    ##realweb: real环境只发布sw机器的web部分
    if env == 'realweb':
        env = 'real'
        is_web = True

    ##定义rsync基本命令
    pwd_file = '/home/porsche/publish/tjds1.passwd' if is_intra is True else '/Users/tangjianhui/Documents/publish/tjds1.passwd'
    pubish_code_path = get_publish_code_path(app, is_intra)
    if not os.path.exists(pubish_code_path):
        print("Repo path  {} does not exist.".format(pubish_code_path))
        return
    
    Ct = CliTool()
    ##获取APP部署类型
    deploy_type = get_app_deploy_type(pubish_code_path)
    ## febe检查根目录中是否存在.htaccess文件
    if deploy_type == 'febe':
        if os.path.exists(os.path.join(pubish_code_path, '.htaccess')) is False:
            print("文件 .htaccess does not exist.")
            if code_type == 'api' and os.path.exists(os.path.join(pubish_code_path, 'api/root.htaccess')) is True:
                print("文件 .htaccess 不存在，使用api/root.htaccess 复制.")
                cmd = ('cd {} && cp -r api/root.htaccess ./.htaccess '.format(pubish_code_path))
                CliTool.execCommand(cmd)
        
        if os.path.exists(os.path.join(pubish_code_path, '.htaccess')) is False:
            print(".htacces not exists die")
            exit()
        
        fs_dist_dir = {
            'honor-1688-ds': 'web/build',
        }.get(app, 'web/dist')

        if os.path.exists(os.path.join(pubish_code_path, '{}/index.html'.format(fs_dist_dir))) is False and code_type != 'api':
            print("文件 web/dist/index.html does not exist.")
            return
    
    ##检查api目标是否存在
    if code_type in ['web', 'api'] and deploy_type != 'febe':
        print("非前后端分离项目不支持api发布")
        return
    
    ##检查本地发布pub和real分支是否合法
    if (is_intra is False and env in ['pub', 'real']) and mode == 'real' and is_dist_only(app) is False:
        realLocalSrc = pubish_code_path if os.path.exists(pubish_code_path + ".git") else pubish_code_path + 'api/'
        res = os.popen("cd " + realLocalSrc + " && git branch -a | grep '*' | awk '{print $2}'")
        branch = res.read().strip("\n")

        if any(keyword.lower() in branch.lower() for keyword in ['real', 'master', 'revert']) is False:
            print(("清检查分支: {} 是否为正式发布分支? 正式发布分支名称需包含 real、master、revert等关键词".format(branch)))
            exit()

        if is_php8_idc(idc=idc) is False:
            ##检查api目录下git仓库的子模块是否是最新的远程节点
            is_valid, outdated_submodules, has_submodules = CliTool.checkGitSubmodules(realLocalSrc)
            if has_submodules:
                if not is_valid:
                    print("\n[错误] 以下子模块不是最新的远程节点，请更新后再发布:")
                    for sub in outdated_submodules:
                        print("  - {}: 当前 {} != 远程 {}".format(sub['path'], sub['current'], sub['remote']))
                    print("")
                    exit()
            else:
                print("[提示] 该仓库不存在子模块，跳过子模块检查")

    ##发布目标目录
    module = get_pubish_module(app, env, idc)
    
    ##获取多软件分身的分身应用，仅发布sw机器即可
    is_only_pub_sw = is_ay_febe_app(idc, app)

    ##获取机器列表
    file_host_name_path = '{}/conf/hosts/{}'.format(os.path.dirname(sys.argv[0]), get_idc_host_file(idc, app, is_web, is_only_pub_sw))

    if not os.path.exists(file_host_name_path):
        print("Host file {} does not exist.".format(file_host_name_path))
        return

    ##所有的机器列表
    ips_map = CliTool.getIpsMap(file_host_name_path, isIntra = is_intra, defaultPort = 8873, defaultModule = module, forePort=force_port)
    
    ##待发布的环境
    all_env = get_publish_ext_env(env, app)
    env_list = CliTool.getEnvList(all_env, ips_map, is_intra)
    
    ##获取发布的机器列表
    ips = {}
    for ev in env_list:
        if ev not in ips_map:
            continue
        ips = dict(ips, **ips_map[ev])

    ##sw机器
    ips_sw = {key: value for key, value in ips.items() if '-sw' in key}
            
    ##发布joa机器
    if 'joa-real' in ips_map and env == 'real' and mode == 'real':
        joa_ips = ips_map['joa-real']
        joa_ext = build_exclude_ext(ext_mode='api', exclude_atjmax=False)
        cmd_joa_tpl = build_command_tpl('--del', pwd_file, joa_ext, pubish_code_path)
        print("正在发布joa机器 ...")
        Ct.publish(joa_ips , mode, cmd_joa_tpl, useThreadPool=False)
        
    ##发布sw机器(只发布web部分，多软件分身的分身应用如：dianbafang，ksdz等，只发布sw机器)
    if is_web is True:
        pubish_sw(ips_sw, code_type, mode, pwd_file, pubish_code_path)
        return
    
    ##如果是前后端分离的项目先发布sw机器的web部分
    if deploy_type == 'febe' and code_type is None and mode == 'real' and env == 'real':
        pubish_sw(ips_sw, code_type, mode, pwd_file, pubish_code_path)

    ##其他正常发布(暂时手动控制白名单为主)
    exclude_atjmax = True if (env not in ['pre', 'pub', 'test', 'joa-real']) and (is_not_skip_atjaxm_app(app) is False) else False
    default_ext = build_exclude_ext(ext_mode=code_type, exclude_atjmax=exclude_atjmax)
    cmd_tpl_all = build_command_tpl('--del', pwd_file, default_ext, pubish_code_path)
    
    syntax_check = False if (is_intra is True or is_php8_idc(idc=idc) is True) else True

    Ct.publish(ips, mode, cmd_tpl_all, is_intra, php_syntax_check = syntax_check)
   
def start_publish_zk(idc, zk, mode, env, is_intra = False, force_port = None):
    ##定义rsync基本命令
    pwd_file = '/home/porsche/publish/tjds2.passwd'
    

    ##获取机器列表
    file_host_name_path = '{}/conf/hosts/{}'.format(os.path.dirname(sys.argv[0]), get_idc_host_file(idc))
    if not os.path.exists(file_host_name_path):
        print("Host file {} does not exist.".format(file_host_name_path))
        return
    
    ips_map = CliTool.getIpsMap(file_host_name_path, isIntra = is_intra, defaultPort=8875, defaultModule = 's', forePort=force_port)
    
    ##待发布的环境
    all_env = get_publish_zk_ext_env(env, zk)
    env_list = CliTool.getEnvList(all_env, ips_map, is_intra)
    
    ##获取发布的机器列表
    ips = {}
    for env in env_list:
        ips = dict(ips, **ips_map[env])
        
    ##Zk文件
    if zk in ['xiuxiu-node-api', 'ai-ps', 'node-video-v2']:
        zkFile = 'Zk.' + zk  + '.json'
    else:
        zkFile = 'Zk.' + zk  + '.inc'
    
    pubish_code_path = '/alidata/s/' + zkFile
    if not os.path.exists(pubish_code_path):
        print("Zk file [{}] does not exist.".format(pubish_code_path))
        return

    cmdTpl = 'rsync -%s --delay-updates --del --stats --port=%s --password-file=' + pwd_file + ' --timeout=30 ' + pubish_code_path + ' root@%s::%s/' + zkFile
    Ct = CliTool()
    Ct.publish(ips, mode, cmdTpl)

     
if __name__ == '__main__':
    pass