# -*- coding: utf-8 -*-
"""
upload_to_server.py - 上传更新的文件到 192.168.5.60 服务器
"""

import paramiko
import os
import time
from datetime import datetime

# 从环境变量读取配置
HOST = os.environ.get('SSH_HOST', '192.168.5.60')
USER = os.environ.get('SSH_USER', 'ubains')
PASSWORD = os.environ.get('SSH_PASSWORD', '')
REMOTE_BASE = '/opt/troubleshoot'

if not PASSWORD:
    print("错误：SSH_PASSWORD 环境变量未设置")
    print("请设置环境变量或创建 .env 文件")
    exit(1)

# 本地部署目录
# P0-3 后统一数据源在 skill/code/web/，deploy/web/ 已删除
LOCAL_BASE = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "skill", "code", "web"))

# 仓库根目录（用于 deploy/ 下的向量文件等）
REPO_ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))

# 远程 web 目录（与 deploy.sh 的 DEPLOY_DIR/web 对应）
REMOTE_WEB_DIR = REMOTE_BASE + "/web"

# 需要上传的单个文件（相对 LOCAL_BASE）
FILES_TO_UPLOAD = [
    ('server.py', 'web/server.py'),
    ('cache_manager.py', 'web/cache_manager.py'),
    ('decorators.py', 'web/decorators.py'),  # P1-3：url_for('login')→url_for('auth.login')
    ('container.py', 'web/container.py'),    # P1-3：依赖容器
    ('search_engine.py', 'web/search_engine.py'),  # P2-1：向量搜索 + TF-IDF 降级
    ('users.json', 'web/users.json'),        # 用户数据（含密码哈希）
]

# 需要上传的部署文件（相对仓库根目录，非 LOCAL_BASE）
# P2-1：向量索引文件随索引一起部署到 REMOTE_BASE
DEPLOY_FILES_TO_UPLOAD = [
    ('搜索向量.json', ''),  # 远程路径在 REMOTE_BASE 下
]

# 需要上传的目录（相对 LOCAL_BASE → 远程 web 目录下同名子目录）
# P1-1 新增 utils/ 模块；P1-3 新增 routes/ services/ 目录；P1 收尾新增 templates
DIRS_TO_UPLOAD = [
    ('utils', 'web/utils'),          # 含 modules.py / vector_builder.py 等
    ('routes', 'web/routes'),        # 含 platform.py（平台首页）+ service_manage.py（服务管理 API）
    ('services', 'web/services'),    # P1-3：ai_service / record_service（含 service_manage.py）
    ('templates', 'web/templates'),  # 含 platform.html + service_monitor/ 子目录
]

# 需要递归上传的目录（含多层子目录，如服务监测子包）
# service_monitor 是自包含子包：代码 + bash 资产 + 模板均在内
# 排除 tests/（测试）、data/（运行时数据，勿覆盖服务器已有报告/目标）、__pycache__
RECURSIVE_DIRS_TO_UPLOAD = [
    ('service_monitor', 'web/service_monitor'),
]

# 递归上传时排除的目录名与文件后缀
_EXCLUDE_DIRS = {'__pycache__', 'tests', 'data', '.pytest_cache'}
_EXCLUDE_SUFFIXES = ('.pyc', '.pyo')
# 需要自动转换 CRLF→LF 的后缀（bash 脚本在 Linux 服务器执行，不能有 \r）
_UNIX_LINE_END_SUFFIXES = ('.sh', '.template')

# 服务监测模块需要的 Python 依赖（SSH 远程执行 + 定时调度）
_REQUIRED_PACKAGES = ['paramiko', 'cryptography', 'apscheduler', 'croniter', 'pytz']


def _check_and_install_deps(ssh):
    """检查并安装服务器端 Python 依赖。"""
    print("\n  Checking Python dependencies...")
    for pkg in _REQUIRED_PACKAGES:
        stdin, stdout, stderr = ssh.exec_command(
            f"python3 -c 'import {pkg}' 2>&1"
        )
        result = stdout.read().decode()
        if 'ModuleNotFoundError' in result or 'No module named' in result:
            print(f"    [INSTALL] {pkg} 未安装，正在安装...")
            stdin, stdout, stderr = ssh.exec_command(
                f"pip3 install --break-system-packages {pkg} 2>&1"
            )
            install_result = stdout.read().decode()
            if 'Successfully installed' in install_result or f'Requirement already satisfied: {pkg}' in install_result:
                print(f"    [OK] {pkg} 安装成功")
            else:
                print(f"    [WARN] {pkg} 安装可能失败，请检查日志")
        else:
            # 获取版本
            stdin, stdout, stderr = ssh.exec_command(
                f"python3 -c 'import {pkg}; print({pkg}.__version__)' 2>&1"
            )
            version = stdout.read().decode().strip()
            print(f"    [OK] {pkg} 已安装 ({version})")



def _sftp_put_unix_lines(sftp, local_path, remote_path):
    """上传文件，若后缀匹配则自动转换 CRLF→LF。

    Windows 开发环境 .sh/.template 文件含 \\r\\n，
    Linux 服务器 bash 无法执行含 \\r 的脚本，需转为 \\n。
    """
    if local_path.endswith(_UNIX_LINE_END_SUFFIXES):
        with open(local_path, 'rb') as f:
            content = f.read()
        converted = content.replace(b'\r\n', b'\n')
        with sftp.open(remote_path, 'wb') as f:
            f.write(converted)
    else:
        sftp.put(local_path, remote_path)


def _upload_dir_recursive(ssh, sftp, local_dir, remote_dir, remote_rel):
    """递归上传目录（service_monitor 子包用）。

    排除 __pycache__ / tests / data / .pyc 等运行期与测试产物。
    .sh / .template 文件自动转换 CRLF→LF。
    """
    # 远程目录可能含中文/多层，先确保存在
    ssh.exec_command(f'mkdir -p "{remote_dir}"')[1].channel.recv_exit_status()

    for fname in os.listdir(local_dir):
        if fname in _EXCLUDE_DIRS:
            continue
        lpath = os.path.join(local_dir, fname)
        rpath = remote_dir + "/" + fname
        if os.path.isfile(lpath):
            if fname.endswith(_EXCLUDE_SUFFIXES):
                continue
            try:
                _sftp_put_unix_lines(sftp, lpath, rpath)
                print("    [OK] " + remote_rel + "/" + os.path.relpath(lpath, LOCAL_BASE).replace("\\", "/"))
            except Exception as e:
                print("    [FAIL] " + fname + ": " + str(e))
        elif os.path.isdir(lpath):
            ssh.exec_command(f'mkdir -p "{rpath}"')[1].channel.recv_exit_status()
            _upload_dir_recursive(ssh, sftp, lpath, rpath, remote_rel)


def upload_files():
    print("="*60)
    print("  Troubleshoot - Upload Files")
    print("="*60)
    print("Server: " + HOST)
    print("User: " + USER)
    print("Remote: " + REMOTE_BASE)
    print("="*60)

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
        print("\n[1/5] Connecting...")
        ssh.connect(HOST, username=USER, password=PASSWORD)
        print("[OK] Connected")

        print("\n[2/5] Checking dependencies...")
        _check_and_install_deps(ssh)

        print("\n[3/5] Backing up...")
        today = datetime.now().strftime('%Y%m%d')

        print("\n[4/5] Uploading files...")
        sftp = ssh.open_sftp()

        # 确保远程 web 目录存在
        ssh.exec_command(f'mkdir -p {REMOTE_WEB_DIR}')[1].channel.recv_exit_status()
        # 确保远程 utils 目录存在
        ssh.exec_command(f'mkdir -p {REMOTE_BASE}/web/utils')[1].channel.recv_exit_status()

        # 上传单个文件
        for local_rel, remote_rel in FILES_TO_UPLOAD:
            local_path = os.path.join(LOCAL_BASE, local_rel)
            remote_path = REMOTE_BASE + "/" + remote_rel

            if os.path.exists(local_path):
                print("  Uploading: " + local_rel)
                sftp.put(local_path, remote_path)
                print("  [OK] " + remote_path)
            else:
                print("  [FAIL] File not found: " + local_path)

        # 上传部署文件（向量索引等，在 deploy/ 目录下）
        for local_rel, remote_subdir in DEPLOY_FILES_TO_UPLOAD:
            local_path = os.path.join(REPO_ROOT, "deploy", local_rel)
            remote_path = REMOTE_BASE + "/" + remote_subdir + ("/" if remote_subdir else "") + local_rel

            if os.path.exists(local_path):
                print("  Uploading deploy file: " + local_rel)
                sftp.put(local_path, remote_path)
                print("  [OK] " + remote_path)
            else:
                print("  [SKIP] Deploy file not found: " + local_path + " (will use TF-IDF fallback)")

        # 上传目录（递归）
        for local_rel, remote_rel in DIRS_TO_UPLOAD:
            local_dir = os.path.join(LOCAL_BASE, local_rel)
            remote_dir = REMOTE_BASE + "/" + remote_rel
            ssh.exec_command(f'mkdir -p {remote_dir}')[1].channel.recv_exit_status()

            if not os.path.isdir(local_dir):
                print("  [FAIL] Dir not found: " + local_dir)
                continue

            print("  Uploading dir: " + local_rel + "/")
            for fname in os.listdir(local_dir):
                lpath = os.path.join(local_dir, fname)
                rpath = remote_dir + "/" + fname
                if os.path.isfile(lpath) and not fname.endswith('.pyc'):
                    _sftp_put_unix_lines(sftp, lpath, rpath)
                    print("    [OK] " + remote_rel + "/" + fname)
                elif os.path.isdir(lpath):
                    # 子目录也递归上传（如 templates/service_monitor/）
                    _upload_dir_recursive(ssh, sftp, lpath, rpath, remote_rel + "/" + fname)

        # 递归上传子包目录（service_monitor 等含多层子目录）
        for local_rel, remote_rel in RECURSIVE_DIRS_TO_UPLOAD:
            local_dir = os.path.join(LOCAL_BASE, local_rel)
            remote_dir = REMOTE_BASE + "/" + remote_rel
            if not os.path.isdir(local_dir):
                print("  [FAIL] Dir not found: " + local_dir)
                continue
            print("  Uploading package (recursive): " + local_rel + "/")
            _upload_dir_recursive(ssh, sftp, local_dir, remote_dir, remote_rel)

        sftp.close()
        print("\n[OK] Files uploaded")

        print("\n[5/5] Restarting service...")
        # 优先用 systemctl 管理（生产标准方式），自动确保 FLASK_DEBUG=0
        # 若 service 文件缺少 FLASK_DEBUG=0，则注入后再 daemon-reload
        ensure_service_cmd = (
            'svc=/etc/systemd/system/troubleshoot.service; '
            'if [ -f "$svc" ] && ! grep -q "FLASK_DEBUG=0" "$svc"; then '
            '  sudo sed -i "/^ExecStart=/i Environment=FLASK_DEBUG=0" "$svc" 2>/dev/null '
            '  || sed -i "/^ExecStart=/i Environment=FLASK_DEBUG=0" "$svc"; '
            '  sudo systemctl daemon-reload 2>/dev/null || true; '
            'fi; '
            'echo ok'
        )
        stdin, stdout, stderr = ssh.exec_command(ensure_service_cmd)
        stdout.read()

        # 尝试 systemctl 重启（需要 sudo 或已配置）；失败则回退到 nohup
        stdin, stdout, stderr = ssh.exec_command('sudo -n systemctl restart troubleshoot 2>/dev/null && echo SYSTEMD_OK || echo NO_SYSTEMD')
        restart_result = stdout.read().decode().strip()

        if 'SYSTEMD_OK' in restart_result:
            print("[OK] Service restarted via systemctl")
        else:
            # 回退：nohup 启动（显式关闭 debug 模式，避免 reloader 导致 APScheduler 不初始化）
            restart_cmd = (
                'cd /opt/troubleshoot/web && '
                'pkill -f "python.*server.py" || true && '
                'sleep 2 && '
                'FLASK_DEBUG=0 nohup python3 server.py > /tmp/troubleshoot.log 2>&1 &'
            )
            stdin, stdout, stderr = ssh.exec_command(restart_cmd)
            stdout.read()
            print("[OK] Service restarted via nohup (FLASK_DEBUG=0)")

        print("\nWaiting for service...")
        time.sleep(3)

        print("\n[6/6] Verifying...")
        stdin, stdout, stderr = ssh.exec_command('curl -s http://localhost:8088/api/health')
        result = stdout.read().decode()

        if result and 'ok' in result:
            print("[OK] Service is running")
            print("\nHealth check response:")
            print(result[:300])
        else:
            print("[FAIL] Service may not be running")
            stdin, stdout, stderr = ssh.exec_command('tail -20 /tmp/troubleshoot.log')
            print(stderr.read().decode())

        print("\n" + "="*60)
        print("  Deployment Complete!")
        print("="*60)
        print("URL: http://" + HOST + ":8088")
        print("Health: http://" + HOST + ":8088/api/health")
        print("="*60)

    except Exception as e:
        print("\n[ERROR] " + str(e))
        import traceback
        traceback.print_exc()

    finally:
        ssh.close()


if __name__ == '__main__':
    upload_files()
