#!/usr/bin/env python3
"""
GitLink CLI Demo Server — 子任务一 交互式展示
启动后访问 http://127.0.0.1:8765
"""

import http.server
import json
import subprocess
import os
import sys
import tempfile
import threading
import shutil
import uuid
from pathlib import Path

PORT = 8765
WORKING_DIR = r"D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli"
HTML_DIR = Path(__file__).parent

# ── 定位 Claude Code CLI ─────────────────────────
def _find_claude() -> str | None:
    """查找 claude 可执行文件，优先返回 .exe 以避开 .cmd 的编码问题"""
    # 1) 直接找 claude.exe（npm 全局安装路径）
    candidates = [
        # npm global on Windows
        Path(os.environ.get("APPDATA", "")) / r"npm\node_modules\@anthropic-ai\claude-code\bin\claude.exe",
        # 直接 which（可能返回 .cmd）
        shutil.which("claude"),
        shutil.which("claude.exe"),
    ]
    for p in candidates:
        if p and Path(str(p)).is_file():
            return str(p)

    # 2) 尝试 shutil.which 返回的 .cmd 对应的 .exe
    cmd = shutil.which("claude.cmd")
    if cmd:
        exe = Path(cmd).with_suffix(".exe")
        if exe.is_file():
            return str(exe)

    return None

CLAUDE_EXE = _find_claude()

# 全局：当前正在运行的进程（用于取消）
_proc_lock = threading.Lock()
_current_proc = None


class DemoHandler(http.server.BaseHTTPRequestHandler):
    """HTTP 请求处理器：静态文件 + API 端点"""

    # ── 日志 ────────────────────────────────────────────
    def log_message(self, fmt, *args):
        print(f"[{self.log_date_time_string()}] {args[0]}")

    # ── GET ─────────────────────────────────────────────
    def do_GET(self):
        path = self.path.split("?")[0]
        if path in ("/", "/index.html"):
            self._serve_file("index.html", "text/html; charset=utf-8")
        else:
            self.send_error(404)

    # ── POST ────────────────────────────────────────────
    def do_POST(self):
        if self.path == "/api/exec":
            self._handle_exec()
        elif self.path == "/api/claude":
            self._handle_claude()
        elif self.path == "/api/cancel":
            self._handle_cancel()
        else:
            self.send_error(404)

    # ── OPTIONS (CORS preflight) ────────────────────────
    def do_OPTIONS(self):
        self._send_cors_headers()
        self.send_response(204)
        self.end_headers()

    # ── 取消当前命令 ────────────────────────────────────
    def _handle_cancel(self):
        global _current_proc
        with _proc_lock:
            proc = _current_proc
        if proc is None or proc.poll() is not None:
            self._send_json({"ok": True, "message": "没有正在运行的命令"})
            return
        try:
            proc.kill()
            self._send_json({"ok": True, "message": "已发送终止信号"})
        except Exception as e:
            self._send_json({"ok": False, "error": str(e)})

    # ── Claude Code 执行 ────────────────────────────────
    def _handle_claude(self):
        global _current_proc

        content_length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(content_length)
        try:
            data = json.loads(body)
        except json.JSONDecodeError:
            self._send_json({"ok": False, "error": "Invalid JSON body"})
            return

        prompt = data.get("prompt", "").strip()
        if not prompt:
            self._send_json({"ok": False, "error": "Empty prompt", "session_id": data.get("session_id", "")})
            return

        timeout = min(data.get("timeout", 600), 600)  # 10 min for Claude

        if not CLAUDE_EXE:
            self._send_json({
                "ok": False, "stdout": "",
                "stderr": "❌ 找不到 Claude Code CLI（claude.exe）。请确认已通过 npm 安装：npm install -g @anthropic-ai/claude-code",
                "exit_code": -1,
                "session_id": "",
            })
            return

        # ── 会话管理：支持多轮对话 ──
        session_id = data.get("session_id", "").strip()
        new_session = data.get("new_session", False)

        if new_session or not session_id:
            # 新会话：生成 UUID
            session_id = str(uuid.uuid4())
            args = [CLAUDE_EXE, "-p", prompt, "--session-id", session_id, "--permission-mode", "bypassPermissions"]
        else:
            # 继续已有会话
            args = [CLAUDE_EXE, "-p", prompt, "--resume", session_id, "--permission-mode", "bypassPermissions"]

        proc = None
        try:
            proc = subprocess.Popen(
                args,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding="utf-8",
                errors="replace",
                cwd=WORKING_DIR,
                env={**os.environ, "NO_COLOR": "1"},
            )

            # 注册为当前进程（允许取消）
            with _proc_lock:
                _current_proc = proc

            try:
                stdout, stderr = proc.communicate(timeout=timeout)
                exit_code = proc.returncode
                killed = False
            except subprocess.TimeoutExpired:
                proc.kill()
                stdout, stderr = proc.communicate()
                exit_code = -1
                killed = True

            if killed:
                self._send_json({
                    "ok": False,
                    "stdout": stdout or "",
                    "stderr": f"❌ Claude Code 执行超时（超过 {timeout} 秒）已被终止",
                    "exit_code": -1,
                    "session_id": session_id,
                })
            else:
                self._send_json({
                    "ok": exit_code == 0,
                    "stdout": stdout,
                    "stderr": stderr,
                    "exit_code": exit_code,
                    "session_id": session_id,
                })

        except FileNotFoundError:
            self._send_json({
                "ok": False, "stdout": "",
                "stderr": "❌ 找不到 Claude Code CLI（claude.exe）。请确认已通过 npm 安装：npm install -g @anthropic-ai/claude-code",
                "exit_code": -1,
                "session_id": session_id,
            })
        except Exception as e:
            self._send_json({
                "ok": False, "stdout": "",
                "stderr": f"❌ {e}", "exit_code": -1,
                "session_id": session_id,
            })
        finally:
            with _proc_lock:
                if _current_proc is proc:
                    _current_proc = None

    # ── 命令执行 ────────────────────────────────────────
    def _handle_exec(self):
        global _current_proc

        content_length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(content_length)
        try:
            data = json.loads(body)
        except json.JSONDecodeError:
            self._send_json({"ok": False, "error": "Invalid JSON body"})
            return

        command = data.get("command", "").strip()
        if not command:
            self._send_json({"ok": False, "error": "Empty command"})
            return

        timeout = min(data.get("timeout", 120), 300)  # max 5 min

        tmp = None
        proc = None
        try:
            # 注入 UTF-8 编码设置：避免 PowerShell 默认 GBK 解码含中文 JSON 导致
            # ConvertFrom-Json 报 ArgumentException。对已有该设置的命令重复无害。
            command = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n" + command

            tmp = tempfile.NamedTemporaryFile(
                mode="w", suffix=".ps1", delete=False, encoding="utf-8-sig"
            )
            tmp.write(command)
            tmp.close()

            proc = subprocess.Popen(
                [
                    "powershell",
                    "-ExecutionPolicy", "Bypass",
                    "-NoProfile",
                    "-File", tmp.name,
                ],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding="utf-8",
                errors="replace",
                cwd=WORKING_DIR,
                env={**os.environ, "NO_COLOR": "1"},
            )

            # 注册为当前进程（允许取消）
            with _proc_lock:
                _current_proc = proc

            try:
                stdout, stderr = proc.communicate(timeout=timeout)
                exit_code = proc.returncode
                killed = False
            except subprocess.TimeoutExpired:
                proc.kill()
                stdout, stderr = proc.communicate()
                exit_code = -1
                killed = True

            if killed:
                self._send_json({
                    "ok": False,
                    "stdout": stdout or "",
                    "stderr": f"❌ 命令执行超时（超过 {timeout} 秒）已被终止",
                    "exit_code": -1,
                })
            else:
                self._send_json({
                    "ok": exit_code == 0,
                    "stdout": stdout,
                    "stderr": stderr,
                    "exit_code": exit_code,
                })

        except FileNotFoundError:
            self._send_json({
                "ok": False, "stdout": "",
                "stderr": "❌ 找不到 PowerShell，请确认系统已安装 PowerShell",
                "exit_code": -1,
            })
        except Exception as e:
            self._send_json({
                "ok": False, "stdout": "",
                "stderr": f"❌ {e}", "exit_code": -1,
            })
        finally:
            with _proc_lock:
                if _current_proc is proc:
                    _current_proc = None
            if tmp:
                try:
                    os.unlink(tmp.name)
                except OSError:
                    pass

    # ── 辅助方法 ────────────────────────────────────────
    def _serve_file(self, filename: str, content_type: str):
        filepath = HTML_DIR / filename
        try:
            with open(filepath, "rb") as f:
                content = f.read()
            self.send_response(200)
            self.send_header("Content-Type", content_type)
            self.send_header("Content-Length", str(len(content)))
            self.send_header("Cache-Control", "no-cache")
            self._send_cors_headers()
            self.end_headers()
            self.wfile.write(content)
        except FileNotFoundError:
            self.send_error(404)

    def _send_json(self, data: dict):
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self._send_cors_headers()
        self.end_headers()
        self.wfile.write(body)

    def _send_cors_headers(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")


def main():
    if not os.path.isdir(WORKING_DIR):
        print(f"⚠️  警告：工作目录不存在 — {WORKING_DIR}")
        print("   请修改 server.py 中的 WORKING_DIR 变量")
        sys.exit(1)

    server = http.server.ThreadingHTTPServer(("127.0.0.1", PORT), DemoHandler)

    claude_status = f"✅ {CLAUDE_EXE}" if CLAUDE_EXE else "❌ 未找到 claude.exe（Skills 功能不可用）"
    print(f"""
╔════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                  GitLink CLI 作品展示 · 交互式平台                                         ║
╠════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║  打开浏览器访问: http://127.0.0.1:{PORT}                                                                     ║
║  工作目录: {WORKING_DIR}                                 ║
║  Claude Code: {claude_status:<55} ║
║  按 Ctrl+C 停止服务器                                                                                      ║
╚════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
""")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n👋 服务器已停止。")


if __name__ == "__main__":
    main()
