# -*- coding: utf-8 -*-
"""Terminal launch and foreground command helpers."""

from .shared import *
from .erlang import _ensure_epmd_daemon

def _batch_echo_text(text: str) -> str:
    """Escape cmd.exe metacharacters for display-only echo lines in a .bat file."""
    escaped = str(text).replace('^', '^^')
    for char in ('&', '|', '<', '>', '(', ')'):
        escaped = escaped.replace(char, f'^{char}')
    escaped = escaped.replace('%', '%%')
    return escaped


def _create_windows_command_batch(cmd: str, working_dir: str, window_title: str) -> str:
    """Create the temporary .bat used by both cmd.exe windows and Windows Terminal tabs."""
    safe_title = _batch_echo_text(window_title)
    safe_cmd_for_echo = _batch_echo_text(cmd)
    bat_content = f'''@echo off
title {safe_title}
cd /d "{working_dir}"
set ESCRIPT_EMULATOR=erl
echo ========================================
echo Command: {safe_cmd_for_echo}
echo ========================================
{cmd}
'''

    with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False, encoding='utf-8') as f:
        f.write(bat_content)
        return f.name


def _normalize_windows_terminal_mode(mode: Optional[str] = None) -> str:
    raw = (mode or os.environ.get('SERVER_MANAGER_TERMINAL') or 'auto').strip().lower()
    aliases = {
        '': 'auto',
        'default': 'auto',
        'wt': 'wt',
        'windows_terminal': 'wt',
        'windows-terminal': 'wt',
        'tabbed': 'wt',
        'classic': 'cmd',
        'console': 'cmd',
        'cmd.exe': 'cmd',
        'powershell.exe': 'powershell',
        'ps': 'powershell',
        'pwsh': 'pwsh',
        'pwsh.exe': 'pwsh',
        'custom': 'other',
    }
    normalized = aliases.get(raw, raw)
    if normalized in {'auto', 'wt', 'cmd', 'powershell', 'pwsh', 'other'}:
        return normalized
    return 'auto'


def _find_windows_terminal_executable() -> Optional[str]:
    """Locate wt.exe without assuming the WindowsApps alias directory is on PATH."""
    candidates: List[Optional[Union[str, Path]]] = [
        shutil.which('wt.exe'),
        shutil.which('wt'),
    ]
    local_app_data = os.environ.get('LOCALAPPDATA')
    if local_app_data:
        candidates.append(Path(local_app_data) / 'Microsoft' / 'WindowsApps' / 'wt.exe')

    for candidate in candidates:
        if not candidate:
            continue
        path = Path(candidate)
        if path.exists():
            return str(path)
    return None


def _ps_single_quoted(value: str) -> str:
    """Quote a string as a PowerShell single-quoted literal."""
    return "'" + str(value).replace("'", "''") + "'"


def _resolve_windows_powershell_executable(powershell_exe: str) -> Optional[str]:
    """Resolve a PowerShell executable, with Windows PowerShell as the stable fallback."""
    raw = str(powershell_exe or '').strip()
    candidates: List[Optional[Union[str, Path]]] = []

    if raw:
        expanded = os.path.expandvars(os.path.expanduser(raw.strip('"')))
        raw_path = Path(expanded)
        if raw_path.is_absolute():
            candidates.append(raw_path)
        candidates.append(shutil.which(raw))

    wants_pwsh = raw.lower() in {'pwsh', 'pwsh.exe'}
    if wants_pwsh:
        candidates.extend([shutil.which('pwsh.exe'), shutil.which('pwsh')])

    candidates.extend([shutil.which('powershell.exe'), shutil.which('powershell')])
    system_root = os.environ.get('SystemRoot') or os.environ.get('WINDIR')
    if system_root:
        candidates.append(
            Path(system_root) / 'System32' / 'WindowsPowerShell' / 'v1.0' / 'powershell.exe'
        )

    for candidate in candidates:
        if not candidate:
            continue
        path = Path(candidate)
        if path.exists():
            return str(path)
    return None


def _resolve_windows_custom_terminal_executable(configured_exe: Optional[str]) -> str:
    raw = str(configured_exe or '').strip()
    if not raw:
        raw = (
            os.environ.get('SERVER_MANAGER_TERMINAL_EXE', '')
            or os.environ.get('SERVER_MANAGER_TERMINAL_COMMAND', '')
        ).strip()
    if not raw:
        raise ValueError('启动方式为“其他”时，请先配置运行 EXE')

    expanded = os.path.expandvars(os.path.expanduser(raw.strip('"')))
    path = Path(expanded)
    if path.is_absolute() or any(sep in expanded for sep in ('/', '\\')):
        if not path.is_file():
            raise FileNotFoundError(f'运行 EXE 不存在: {expanded}')
        return str(path.resolve())

    legacy_cwd = os.environ.get('SERVER_MANAGER_TERMINAL_CWD', '').strip()
    if legacy_cwd:
        legacy_path = Path(os.path.expandvars(os.path.expanduser(legacy_cwd))) / raw
        if legacy_path.is_file():
            return str(legacy_path.resolve())

    found = shutil.which(raw)
    if found:
        return found
    raise FileNotFoundError(f'未在 PATH 中找到运行 EXE: {raw}')


def _windows_short_path(path: str) -> str:
    """Return an 8.3 path when Windows can provide one; fall back to the original path."""
    if not IS_WINDOWS:
        return path
    try:
        import ctypes
        from ctypes import wintypes

        get_short_path_name = ctypes.windll.kernel32.GetShortPathNameW
        get_short_path_name.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
        get_short_path_name.restype = wintypes.DWORD
        size = get_short_path_name(path, None, 0)
        if size == 0:
            return path
        buffer = ctypes.create_unicode_buffer(size + 1)
        result = get_short_path_name(path, buffer, size + 1)
        return buffer.value if result else path
    except Exception:
        return path


def _mobaxterm_posix_path(path: str) -> str:
    """Convert a Windows path to the /drives/x form used by MobaXterm local shell."""
    raw = str(path).replace('\\', '/')
    if len(raw) >= 3 and raw[1:3] == ':/':
        drive = raw[0].lower()
        rest = raw[3:].lstrip('/')
        while '//' in rest:
            rest = rest.replace('//', '/')
        return f'/drives/{drive}/{rest}'
    return raw


def _windows_slash_path(path: str) -> str:
    return str(path).replace('\\', '/')


def _sh_single_quoted(value: str) -> str:
    return "'" + str(value).replace("'", "'\"'\"'") + "'"


def _build_windows_custom_terminal_args(exe_path: str, bat_path: str) -> List[str]:
    """Build argv for known terminal executables selected through the simplified EXE setting."""
    name = Path(exe_path).name.lower()
    if name in {'wt.exe', 'wt'}:
        return [exe_path, 'new-tab', 'cmd', '/k', bat_path]
    if name in {'cmd.exe', 'cmd'}:
        return [exe_path, '/k', bat_path]
    return [exe_path, bat_path]


def _create_windows_powershell_batch_runner(
    bat_path: str,
    working_dir: str,
    window_title: str,
    cmd: Optional[str] = None,
) -> str:
    display_cmd = cmd or str(Path(bat_path).resolve())
    ps_content = f'''$ErrorActionPreference = 'Continue'
try {{
    $Host.UI.RawUI.WindowTitle = {_ps_single_quoted(window_title)}
}} catch {{}}
Set-Location -LiteralPath {_ps_single_quoted(working_dir)}
$env:ESCRIPT_EMULATOR = 'erl'
Write-Host '========================================'
Write-Host ('Command: ' + {_ps_single_quoted(display_cmd)})
Write-Host ('WorkingDir: ' + {_ps_single_quoted(working_dir)})
Write-Host '========================================'
$batPath = {_ps_single_quoted(bat_path)}
$cmdExe = $env:ComSpec
if ([string]::IsNullOrWhiteSpace($cmdExe)) {{
    $cmdExe = 'cmd.exe'
}}
& $cmdExe /c $batPath
'''
    with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1', delete=False, encoding='utf-8-sig') as f:
        f.write(ps_content)
        return f.name


def _create_mobaxterm_launcher_script(
    powershell_exe_for_moba: str,
    ps_script_for_windows: str,
    working_dir_for_moba: str,
) -> str:
    """Create the MobaXterm local-shell script that launches Windows PowerShell."""
    sh_content = (
        "#!/bin/sh\n"
        f"cd {_sh_single_quoted(working_dir_for_moba)} || exit 1\n"
        f"exec {_sh_single_quoted(powershell_exe_for_moba)} "
        "-NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass "
        f"-File {_sh_single_quoted(ps_script_for_windows)}\n"
    )
    with tempfile.NamedTemporaryFile(
        mode='w',
        suffix='.sh',
        delete=False,
        encoding='utf-8',
        newline='\n',
    ) as f:
        f.write(sh_content)
        return f.name


def _build_windows_mobaxterm_args(
    exe_path: str,
    bat_path: str,
    working_dir: str,
    window_title: str,
    cmd: str,
) -> List[str]:
    ps_path = _create_windows_powershell_batch_runner(bat_path, working_dir, window_title, cmd)
    powershell_exe = _resolve_windows_powershell_executable('powershell.exe') or 'powershell.exe'
    ps_exe_for_moba = _mobaxterm_posix_path(powershell_exe)
    ps_script_for_windows = _windows_slash_path(_windows_short_path(ps_path))
    workdir_for_moba = _mobaxterm_posix_path(working_dir)
    launcher_path = _create_mobaxterm_launcher_script(
        ps_exe_for_moba,
        ps_script_for_windows,
        workdir_for_moba,
    )
    launcher_for_moba = _mobaxterm_posix_path(_windows_short_path(launcher_path))
    return [exe_path, '-newtab', f'sh {_sh_single_quoted(launcher_for_moba)}']


def _read_pid_file(pid_file: str, timeout: float = 3.0) -> Optional[int]:
    deadline = time.time() + timeout
    while time.time() < deadline:
        try:
            text = Path(pid_file).read_text(encoding='ascii', errors='ignore').strip()
            if text:
                return int(text.splitlines()[0].strip())
        except (OSError, ValueError):
            pass
        time.sleep(0.05)
    return None


def _launch_windows_cmd_console(bat_path: str, working_dir: str) -> str:
    """Launch the temp batch in a classic cmd.exe window."""
    CREATE_NEW_CONSOLE = 0x00000010
    try:
        proc = subprocess.Popen(
            ['cmd', '/k', bat_path],
            creationflags=CREATE_NEW_CONSOLE,
            cwd=working_dir,
        )
        return f"winpid:{proc.pid}"
    except Exception:
        # 兜底：回到经典方式（失去 PID 追踪能力，关闭只能按标题）
        subprocess.Popen(f'start cmd /k "{bat_path}"', shell=True)
        return bat_path


def _launch_windows_powershell_console(
    bat_path: str,
    working_dir: str,
    window_title: str,
    powershell_exe: str = 'powershell.exe',
) -> str:
    """Launch the temp batch in a PowerShell window and keep it open."""
    resolved_powershell = _resolve_windows_powershell_executable(powershell_exe)
    if not resolved_powershell:
        return _launch_windows_cmd_console(bat_path, working_dir)

    ps_content = f'''$ErrorActionPreference = 'Continue'
try {{
    $Host.UI.RawUI.WindowTitle = {_ps_single_quoted(window_title)}
}} catch {{}}
Set-Location -LiteralPath {_ps_single_quoted(working_dir)}
$env:ESCRIPT_EMULATOR = 'erl'
Write-Host '========================================'
Write-Host ('Command: ' + {_ps_single_quoted(str(Path(bat_path).resolve()))})
Write-Host '========================================'
$batPath = {_ps_single_quoted(bat_path)}
$cmdExe = $env:ComSpec
if ([string]::IsNullOrWhiteSpace($cmdExe)) {{
    $cmdExe = 'cmd.exe'
}}
& $cmdExe /k $batPath
'''
    with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1', delete=False, encoding='utf-8-sig') as f:
        f.write(ps_content)
        ps_path = f.name

    CREATE_NEW_CONSOLE = 0x00000010
    try:
        proc = subprocess.Popen(
            [
                resolved_powershell,
                '-NoLogo',
                '-NoProfile',
                '-NoExit',
                '-ExecutionPolicy', 'Bypass',
                '-File', ps_path,
            ],
            creationflags=CREATE_NEW_CONSOLE,
            cwd=working_dir,
        )
        return f"winpid:{proc.pid}"
    except Exception:
        return _launch_windows_cmd_console(bat_path, working_dir)


def _launch_windows_custom_terminal(
    bat_path: str,
    working_dir: str,
    window_title: str,
    cmd: str,
    terminal_exe: Optional[str] = None,
) -> str:
    del cmd
    exe_path = _resolve_windows_custom_terminal_executable(terminal_exe)
    exe_name = Path(exe_path).name.lower()
    if 'mobaxterm' in exe_name:
        args = _build_windows_mobaxterm_args(exe_path, bat_path, working_dir, window_title, cmd)
    else:
        args = _build_windows_custom_terminal_args(exe_path, bat_path)
    launch_cwd = working_dir if Path(working_dir).is_dir() else str(Path(exe_path).resolve().parent)
    CREATE_NEW_CONSOLE = 0x00000010
    proc = subprocess.Popen(
        args,
        creationflags=CREATE_NEW_CONSOLE,
        cwd=launch_cwd,
    )
    return f"winpid:{proc.pid}"


def _launch_windows_terminal_tab(bat_path: str, working_dir: str, window_title: str,
                                 mode: Optional[str] = None) -> Optional[str]:
    """Launch the temp batch in a named Windows Terminal window as a new tab.

    The tab runs through a small PowerShell wrapper so the tool can capture a
    real process PID and later close this tab's process tree without killing the
    whole Windows Terminal window.
    """
    mode = _normalize_windows_terminal_mode(mode)
    if mode in {'cmd', 'powershell', 'pwsh', 'other'}:
        return None

    wt_exe = _find_windows_terminal_executable()
    if not wt_exe:
        if mode == 'wt':
            raise FileNotFoundError('未找到 Windows Terminal (wt.exe)')
        return None

    pid_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pid')
    pid_file.close()
    ps_content = f'''$ErrorActionPreference = 'Continue'
try {{
    Set-Content -LiteralPath {_ps_single_quoted(pid_file.name)} -Value $PID -Encoding ascii
}} catch {{}}
try {{
    $Host.UI.RawUI.WindowTitle = {_ps_single_quoted(window_title)}
}} catch {{}}
Set-Location -LiteralPath {_ps_single_quoted(working_dir)}
$env:ESCRIPT_EMULATOR = 'erl'
Write-Host '========================================'
Write-Host ('Command: ' + {_ps_single_quoted(str(Path(bat_path).resolve()))})
Write-Host '========================================'
& $env:ComSpec /k {_ps_single_quoted(bat_path)}
'''
    with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1', delete=False, encoding='utf-8-sig') as f:
        f.write(ps_content)
        ps_path = f.name

    window_id = os.environ.get('SERVER_MANAGER_WT_WINDOW_ID', 'ServerManager')
    args = [
        wt_exe,
        '-w', window_id,
        'new-tab',
        '--title', window_title,
        '--suppressApplicationTitle',
        '-d', working_dir,
        'powershell.exe',
        '-NoLogo',
        '-ExecutionPolicy', 'Bypass',
        '-File', ps_path,
    ]
    proc = subprocess.Popen(
        args,
        cwd=working_dir,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    pid = _read_pid_file(pid_file.name)
    if pid is not None:
        try:
            os.unlink(pid_file.name)
        except OSError:
            pass
        return f"wtpid:{pid}"

    # If wt rejected the command line quickly, let the caller fall back to cmd.
    try:
        if proc.poll() not in (None, 0):
            return None
    except Exception:
        pass
    return f"wttab:{window_title}"


def run_cmd_in_new_window(cmd: str, working_dir: str, window_title: str = "Server", 
                          erl_path: str = None,
                          terminal_mode: str = None,
                          terminal_exe_path: str = None) -> str:
    """在新的终端窗口中执行命令
    
    Windows: 可配置 Windows Terminal/cmd/PowerShell/其他启动方式
    Linux: 直接在当前终端执行，或使用 screen/tmux（如果可用）
    
    Args:
        cmd: 要执行的命令
        working_dir: 工作目录
        window_title: 窗口标题
        erl_path: Erlang 安装路径（命令中已包含完整路径，此参数保留兼容）
    
    Returns:
        脚本文件路径（Windows）或空字符串（Linux）
    """
    if IS_WINDOWS:
        bat_path = _create_windows_command_batch(cmd, working_dir, window_title)
        mode = _normalize_windows_terminal_mode(terminal_mode)

        if mode == 'other':
            try:
                return _launch_windows_custom_terminal(
                    bat_path,
                    working_dir,
                    window_title,
                    cmd,
                    terminal_exe_path,
                )
            except Exception:
                return _launch_windows_cmd_console(bat_path, working_dir)

        if mode in {'powershell', 'pwsh'}:
            ps_exe = 'pwsh.exe' if mode == 'pwsh' else 'powershell.exe'
            return _launch_windows_powershell_console(bat_path, working_dir, window_title, ps_exe)

        if mode == 'cmd':
            return _launch_windows_cmd_console(bat_path, working_dir)

        # 优先使用 Windows Terminal 的命名窗口页签；没有 wt.exe 或启动失败时，
        # 回退到原来的独立 cmd.exe 窗口。
        try:
            launch_info = _launch_windows_terminal_tab(bat_path, working_dir, window_title, mode)
            if launch_info:
                return launch_info
        except Exception:
            if mode == 'wt':
                raise

        # 使用 CREATE_NEW_CONSOLE 启动新控制台窗口，Popen.pid 即为 cmd.exe 的真实 PID。
        # 便于后续 taskkill /F /T /PID 精确关闭。
        return _launch_windows_cmd_console(bat_path, working_dir)
    else:
        # Linux: 直接在终端执行
        return run_cmd_in_terminal_linux(cmd, working_dir, window_title, erl_path)


def run_cmd_in_terminal_linux(cmd: str, working_dir: str, window_title: str = "Server",
                              erl_path: str = None) -> str:
    """在 Linux 终端中执行命令
    
    优先使用 screen，其次使用 tmux，最后直接在前台执行
    
    Args:
        cmd: 要执行的命令
        working_dir: 工作目录
        window_title: 窗口/会话名称
        erl_path: Erlang 安装路径（命令中已包含完整路径，此参数保留兼容）
    
    Returns:
        启动方式描述
    """
    # 检查是否有 screen
    has_screen = subprocess.run(['which', 'screen'], capture_output=True).returncode == 0
    # 检查是否有 tmux
    has_tmux = subprocess.run(['which', 'tmux'], capture_output=True).returncode == 0
    
    # 生成 shell 脚本
    script_content = f'''#!/bin/bash
cd "{working_dir}"
export ESCRIPT_EMULATOR=erl
echo "========================================"
echo "Command: {cmd}"
echo "========================================"
{cmd}
'''
    
    # 写入临时脚本文件
    with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False, encoding='utf-8') as f:
        f.write(script_content)
        script_path = f.name
    
    # 赋予执行权限
    os.chmod(script_path, 0o755)
    
    if has_screen:
        # 使用 screen 在后台启动
        session_name = window_title.replace(' ', '_').replace('.', '_')
        subprocess.Popen(
            ['screen', '-dmS', session_name, 'bash', script_path],
            cwd=working_dir
        )
        return f"screen:{session_name}"
    elif has_tmux:
        # 使用 tmux 在后台启动
        session_name = window_title.replace(' ', '_').replace('.', '_')
        subprocess.Popen(
            ['tmux', 'new-session', '-d', '-s', session_name, f'bash {script_path}'],
            cwd=working_dir
        )
        return f"tmux:{session_name}"
    else:
        # 直接在后台执行
        subprocess.Popen(
            ['bash', script_path],
            cwd=working_dir,
            start_new_session=True
        )
        return f"background:{script_path}"


def run_cmd_foreground(cmd: str, working_dir: str, window_title: str = "Server") -> subprocess.Popen:
    """在前台执行命令（跨平台）
    
    用于非交互类前台命令（compile 等）和 Linux 下的所有前台命令。
    Windows 上 rebar3 shell 等交互式命令应由调用方走 run_cmd_in_new_window（start cmd /k）。
    
    Args:
        cmd: 要执行的命令
        working_dir: 工作目录
        window_title: 显示标题
    
    Returns:
        Popen 进程对象
    """
    print(f"========================================")
    print(f"[{window_title}]")
    print(f"工作目录: {working_dir}")
    print(f"命令: {cmd}")
    print(f"========================================")
    
    env = os.environ.copy()
    env['ESCRIPT_EMULATOR'] = 'erl'
    
    if IS_WINDOWS:
        process = subprocess.Popen(cmd, shell=True, cwd=working_dir, env=env)
    else:
        process = subprocess.Popen(
            ['bash', '-c', cmd],
            cwd=working_dir,
            env=env
        )
    
    return process
