#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kylin-mount-tool 后台特权操作统一下发处理器 (Atomic Unified Backend Helper)
通过单次提权事务执行：创建目录、保存凭据、原子写入 fstab、执行挂载与自启注册。
参数支持 Base64 / JSON 命令行传参与 Stdin 双通道。
"""

import sys
import os
import json
import subprocess
import hashlib
import base64
import re

CREDS_DIR = "/etc/kylin-mount-tool"
FSTAB_PATH = "/etc/fstab"

def escape_fstab_path(path):
    return path.replace("\\", "\\134").replace(" ", "\\040").replace("\t", "\\011").replace("\n", "\\012")

def unescape_fstab_path(path):
    return path.replace("\\040", " ").replace("\\011", "\t").replace("\\012", "\n").replace("\\134", "\\")

def do_save_mount(data):
    proto = data.get("proto", "Samba (CIFS)")
    ip = data.get("ip", "").strip()
    port = data.get("port", "").strip()
    share = data.get("share", "").strip()
    local_dir = data.get("local_dir", "").strip()
    user = data.get("user", "").strip()
    passwd = data.get("passwd", "").strip()
    is_anon = data.get("is_anon", False)
    ro = data.get("ro", False)
    smb_vers = data.get("smb_vers", "自动检测")
    enable_autostart = data.get("enable_autostart", True)

    if not ip or not share or not local_dir:
        return {"success": False, "msg": "参数不完整 (IP、共享名或挂载目录为空)"}

    # 1. 创建本地挂载目录
    try:
        os.makedirs(local_dir, exist_ok=True)
    except Exception as e:
        return {"success": False, "msg": "创建本地挂载目录失败: {0}".format(str(e))}

    # 2. 确保安全凭据目录存在
    os.makedirs(CREDS_DIR, mode=0o777, exist_ok=True)

    # 3. 构造 fstab 参数
    h = hashlib.md5(local_dir.encode("utf-8")).hexdigest()
    share_clean = share.strip("/")
    fstab_entry = ""

    if proto == "Samba (CIFS)":
        share_uri = "//{0}/{1}".format(ip, share_clean)
        fs_type = "cifs"
        opts = ["_netdev", "nofail", "x-systemd.automount", "iocharset=utf8", "file_mode=0777", "dir_mode=0777"]
        if ro:
            opts.append("ro")
        else:
            opts.append("rw")
        if port and port != "445":
            opts.append("port={0}".format(port))

        if is_anon or not user:
            opts.append("guest")
        else:
            creds_file = os.path.join(CREDS_DIR, "smb_creds_{0}".format(h))
            with open(creds_file, "w", encoding="utf-8") as cf:
                cf.write("username={0}\npassword={1}\n".format(user, passwd))
            os.chmod(creds_file, 0o600)
            opts.append("credentials={0}".format(creds_file))

        if "1.0" in smb_vers:
            opts.extend(["vers=1.0", "sec=ntlm"])
        elif "2.0" in smb_vers or "2.1" in smb_vers:
            opts.append("vers=2.0")
        elif "3.0" in smb_vers:
            opts.append("vers=3.0")

        opts_str = ",".join(opts)
        fstab_entry = "{0}\t{1}\t{2}\t{3}\t0\t0\t# KYLIN_MOUNT_TOOL_ENTRY\n".format(
            share_uri, escape_fstab_path(local_dir), fs_type, opts_str
        )

    elif proto == "NFS":
        share_uri = "{0}:/{1}".format(ip, share_clean)
        fs_type = "nfs"
        opts = ["_netdev", "nofail", "x-systemd.automount", "hard", "intr", "timeo=14"]
        opts.append("ro" if ro else "rw")
        if port and port != "2049":
            opts.append("port={0}".format(port))
        opts_str = ",".join(opts)
        fstab_entry = "{0}\t{1}\t{2}\t{3}\t0\t0\t# KYLIN_MOUNT_TOOL_ENTRY\n".format(
            share_uri, escape_fstab_path(local_dir), fs_type, opts_str
        )

    elif proto == "SFTP (SSHFS)":
        remote_path = "/" + share.lstrip("/")
        share_uri = "sshfs#{0}@{1}:{2}".format(user if user else "root", ip, remote_path)
        fs_type = "fuse.sshfs"
        opts = ["_netdev", "nofail", "allow_other", "reconnect", "ServerAliveInterval=15", "ServerAliveCountMax=3"]
        if port and port != "22":
            opts.append("port={0}".format(port))
        if passwd and not is_anon:
            pwd_file = os.path.join(CREDS_DIR, "sshfs_passwd_{0}".format(h))
            with open(pwd_file, "w", encoding="utf-8") as pf:
                pf.write(passwd)
            os.chmod(pwd_file, 0o600)
            opts.append("password_stdin")
        opts.append("ro" if ro else "rw")
        opts_str = ",".join(opts)
        fstab_entry = "{0}\t{1}\t{2}\t{3}\t0\t0\t# KYLIN_MOUNT_TOOL_ENTRY\n".format(
            share_uri, escape_fstab_path(local_dir), fs_type, opts_str
        )

    elif proto == "FTP (CurlFtpFS)":
        remote_path = "/" + share.lstrip("/")
        if is_anon or not user:
            share_uri = "curlftpfs#anonymous@{0}:{1}{2}".format(ip, port if port else "21", remote_path)
        else:
            share_uri = "curlftpfs#{0}:{1}@{2}:{3}{4}".format(user, passwd, ip, port if port else "21", remote_path)
        fs_type = "fuse"
        opts = ["_netdev", "nofail", "allow_other", "auto_unmount"]
        opts.append("ro" if ro else "rw")
        opts_str = ",".join(opts)
        fstab_entry = "{0}\t{1}\t{2}\t{3}\t0\t0\t# KYLIN_MOUNT_TOOL_ENTRY\n".format(
            share_uri, escape_fstab_path(local_dir), fs_type, opts_str
        )

    # 4. 原子安全更新 /etc/fstab
    lines = []
    if os.path.exists(FSTAB_PATH):
        with open(FSTAB_PATH, "r", encoding="utf-8") as f:
            lines = f.readlines()

    target_esc = escape_fstab_path(local_dir)
    new_lines = []
    for l in lines:
        if "# KYLIN_MOUNT_TOOL_ENTRY" in l:
            parts = l.split()
            if len(parts) >= 2 and parts[1] == target_esc:
                continue
        new_lines.append(l)

    if not new_lines or not new_lines[-1].endswith("\n"):
        new_lines.append("\n")
    new_lines.append(fstab_entry)

    tmp_fstab = FSTAB_PATH + ".kmt.tmp"
    with open(tmp_fstab, "w", encoding="utf-8") as f:
        f.writelines(new_lines)
    os.replace(tmp_fstab, FSTAB_PATH)

    # 5. 立即执行 mount 挂载
    subprocess.run(["systemctl", "daemon-reload"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    res_mount = subprocess.run(["timeout", "8", "mount", local_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

    # 6. 激活后台自愈定时器
    if enable_autostart:
        subprocess.run(["systemctl", "enable", "--now", "kmt-daemon.timer"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    if res_mount.returncode != 0:
        err = (res_mount.stderr or res_mount.stdout or "").strip()
        diagnosis = "请检查服务端共享状态或凭据。"
        
        if "error(13)" in err or "Permission denied" in err:
            diagnosis = "【权限被拒绝】：该 Windows 共享禁止了匿名/Guest 免密访问。请在界面上【取消勾选 匿名/免密 (Guest)】，并输入该 Windows 电脑的用户名和密码！"
        elif "error(111)" in err or "Connection refused" in err:
            diagnosis = "【连接被拒绝】：目标主机未开启共享服务或防火墙拦截了端口。"
        elif "error(2)" in err or "No such file" in err:
            diagnosis = "【路径不存在】：远程共享名称不存在，请点击【探测共享】重新选择正确的共享文件夹或盘符。"
        elif "error(112)" in err or "Host is down" in err:
            diagnosis = "【主机离线】：目标服务器无法连通，请检查 IP 地址和网络连接。"
        elif "error(22)" in err or "Invalid argument" in err:
            diagnosis = "【协议不匹配】：当前 SMB 版本不受支持，请在高级选项中切换其他 SMB 版本 (如 vers=1.0 或 vers=2.0)。"

        return {
            "success": True,
            "warning": True,
            "mount_error": True,
            "raw_error": err,
            "msg": "{0}\n\n(底层系统日志: {1})".format(diagnosis, err)
        }

    return {"success": True, "warning": False, "msg": "挂载成功并已配置开机自动挂载与后台重连自愈。"}


def do_delete_mount(data):
    local_dir = data.get("local_dir", "").strip()
    if not local_dir:
        return {"success": False, "msg": "挂载目录不能为空"}

    # 1. 强制延迟卸载
    subprocess.run(["umount", "-f", "-l", local_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # 2. 从 fstab 彻底清除
    lines = []
    if os.path.exists(FSTAB_PATH):
        with open(FSTAB_PATH, "r", encoding="utf-8") as f:
            lines = f.readlines()

    target_esc = escape_fstab_path(local_dir)
    new_lines = []
    for l in lines:
        if "# KYLIN_MOUNT_TOOL_ENTRY" in l:
            parts = l.split()
            if len(parts) >= 2 and parts[1] == target_esc:
                continue
        new_lines.append(l)

    tmp_fstab = FSTAB_PATH + ".kmt.tmp"
    with open(tmp_fstab, "w", encoding="utf-8") as f:
        f.writelines(new_lines)
    os.replace(tmp_fstab, FSTAB_PATH)

    subprocess.run(["systemctl", "daemon-reload"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # 3. 清理缓存凭证
    h = hashlib.md5(local_dir.encode("utf-8")).hexdigest()
    for fn in ["smb_creds_{0}".format(h), "sshfs_passwd_{0}".format(h)]:
        fp = os.path.join(CREDS_DIR, fn)
        if os.path.exists(fp):
            try:
                os.remove(fp)
            except Exception:
                pass

    return {"success": True, "msg": "已安全卸载并彻底清除挂载项配置。"}


def main():
    try:
        raw_in = ""
        if len(sys.argv) > 1:
            raw_arg = sys.argv[1].strip()
            try:
                raw_in = base64.b64decode(raw_arg.encode("utf-8")).decode("utf-8")
            except Exception:
                raw_in = raw_arg

        if not raw_in:
            raw_in = sys.stdin.read().strip()
        
        if not raw_in:
            print(json.dumps({"success": False, "msg": "未接收到操作指令数据"}))
            sys.exit(1)

        data = json.loads(raw_in)
        action = data.get("action", "")

        if action == "save_mount":
            result = do_save_mount(data)
        elif action == "delete_mount":
            result = do_delete_mount(data)
        else:
            result = {"success": False, "msg": "未知操作指令: {0}".format(action)}

        print(json.dumps(result, ensure_ascii=False))
        sys.exit(0 if result.get("success") else 1)

    except Exception as e:
        print(json.dumps({"success": False, "msg": "执行发生异常: {0}".format(str(e))}, ensure_ascii=False))
        sys.exit(1)


if __name__ == "__main__":
    main()
