"""
NoScrible — PC 剪贴板服务器 v3 (HTTP + TCP + 静态网页托管)
"""
import http.server
import json
import socket
import struct
import threading
import subprocess
import sys
import os
import urllib.request
import urllib.error

HOST = "0.0.0.0"
PORT = 48158          # HTTP 端口（浏览器用）
TCP_PORT = 48159       # TCP 端口（安卓 APP 用）


def get_local_ip():
    """获取本机局域网 IP"""
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except Exception:
        return "127.0.0.1"

# 自己所在的目录（方便找 index.html）
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))


# ========== AI API 代理 ==========
def build_api_url(base_url, fmt):
    """构造完整 AI API 地址，与安卓端逻辑一致"""
    u = base_url.strip()
    while u.endswith("/"):
        u = u[:-1]

    is_volc = "volces.com" in u or "volcengine.com" in u

    if fmt == "responses" or is_volc:
        # 豆包等使用 Responses API
        if u.endswith("/v1/responses"):
            pass
        elif u.endswith("/v1"):
            u += "/responses"
        elif u.endswith("/api/v3"):
            u += "/responses"
        else:
            u += "/v1/responses"
    else:
        # 通用 Chat Completions
        if u.endswith("/v1/chat/completions"):
            pass
        elif u.endswith("/v1"):
            u += "/chat/completions"
        else:
            u += "/v1/chat/completions"
    return u


def call_ai_api(api_url, api_key, model, b64_img, fmt):
    """调用 AI 识别 API，返回 LaTeX 文本"""
    u = build_api_url(api_url, fmt)

    system_prompt = (
        "你是一个数学公式识别专家。"
        "请识别图片中的数学公式，输出为 LaTeX 代码。"
        "仅输出 LaTeX 代码本身，不要任何解释、不要 markdown 标记。"
    )

    is_volc = "volces.com" in u or "volcengine.com" in u

    if fmt == "responses" or is_volc:
        # 新版 API 格式（豆包等）
        body = json.dumps({
            "model": model,
            "input": [{
                "role": "user",
                "content": [
                    {"type": "input_image", "image_url": "data:image/png;base64," + b64_img},
                    {"type": "input_text", "text": system_prompt}
                ]
            }],
            "max_output_tokens": 4096,
            "temperature": 0.1
        })
    else:
        # 标准 Chat 格式
        body = json.dumps({
            "model": model,
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": [
                    {"type": "text", "text": "请识别这张图片中的数学公式，输出 LaTeX 代码。"},
                    {"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64_img}}
                ]}
            ],
            "max_tokens": 1024,
            "temperature": 0.1
        })

    req = urllib.request.Request(
        u,
        data=body.encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer " + api_key
        },
        method="POST"
    )

    try:
        with urllib.request.urlopen(req, timeout=60) as resp:
            result = json.loads(resp.read().decode("utf-8"))

        latex = ""
        if fmt == "responses" or "volces.com" in api_url or "volcengine.com" in api_url:
            # Responses API 格式（豆包等）
            output = result.get("output", []) or []
            for entry in output:
                if entry.get("type") != "message":
                    continue
                content_list = entry.get("content", []) or []
                for c in content_list:
                    if c.get("type") in ("text", "output_text"):
                        latex = c.get("text", "")
                        break
                if latex:
                    break
            # 兜底：choices 格式
            if not latex:
                latex = result.get("choices", [{}])[0].get("message", {}).get("content", "")
        else:
            latex = (result.get("choices") or [{}])[0].get("message", {}).get("content", "")

        if latex:
            latex = latex.replace("```latex", "").replace("```", "").strip()
        if not latex:
            raise Exception("API 返回空结果")
        return latex

    except urllib.error.HTTPError as e:
        err_body = e.read().decode("utf-8", errors="replace")
        raise Exception(f"API HTTP {e.code}: {err_body[:200]}")
    except urllib.error.URLError as e:
        raise Exception(f"API 连接失败: {e.reason}")

# ========== HTTP 处理 ==========
class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        cors = {"Access-Control-Allow-Origin": "*"}

        if self.path == "/ping":
            client = self.client_address[0]
            print(f"\n✅ [连接测试] 设备 {client} 连接成功！")
            self._json(200, cors, {"status": "ok"})
        elif self.path == "/" or self.path == "/index.html":
            self._file(200, cors,
                       os.path.join(SCRIPT_DIR, "index.html"),
                       "text/html;charset=utf-8")
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        cors = {"Access-Control-Allow-Origin": "*"}
        try:
            length_str = self.headers.get("Content-Length", "0")
            length = int(length_str) if length_str else 0
            raw = self.rfile.read(length)

            if self.path == "/send":
                client = self.client_address[0]
                body = raw.decode("utf-8")
                to_clipboard(body)
                print(f"\n✅ [公式接收] 来自 {client}")
                print(f"   LaTeX: {body[:80]}...")
                print(f"   📋 已写入剪贴板！")
                self._json(200, cors, {"status": "ok"})

            elif self.path == "/recognize":
                client = self.client_address[0]
                data = json.loads(raw.decode("utf-8"))
                api_url = data["apiUrl"]
                api_key = data["apiKey"]
                model = data["model"]
                b64_img = data["image"]
                fmt = data.get("fmt", "chat")
                print(f"\n🤖 [AI代理] 来自 {client}, 模型: {model}")
                latex = call_ai_api(api_url, api_key, model, b64_img, fmt)
                print(f"   LaTeX: {latex[:80]}...")
                self._json(200, cors, {"latex": latex})

            else:
                self.send_response(404)
                self.end_headers()
        except Exception as e:
            print(f"\n❌ POST 处理出错: {e}")
            self._json(500, cors, {"error": str(e)})

    def do_OPTIONS(self):
        self.send_response(200)
        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")
        self.send_header("Access-Control-Max-Age", "86400")
        self.end_headers()

    def _json(self, code, headers, data):
        self.send_response(code)
        for k, v in headers.items(): self.send_header(k, v)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def _file(self, code, headers, path, mime):
        try:
            with open(path, "rb") as f:
                content = f.read()
            self.send_response(code)
            for k, v in headers.items(): self.send_header(k, v)
            self.send_header("Content-Type", mime)
            self.end_headers()
            self.wfile.write(content)
        except FileNotFoundError:
            self._json(404, headers, {"error": "index.html not found"})

    def log_message(self, format, *args):
        pass  # 安静


# ========== TCP 处理（原生 APP） ==========
def tcp_server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((HOST, TCP_PORT))
    s.listen(5)
    while True:
        try:
            conn, addr = s.accept()
            threading.Thread(target=handle_tcp, args=(conn, addr), daemon=True).start()
        except:
            break

def handle_tcp(conn, addr):
    try:
        header = conn.recv(4)
        if len(header) < 4: return
        length = struct.unpack(">I", header)[0]
        data = b""
        while len(data) < length:
            chunk = conn.recv(length - len(data))
            if not chunk: break
            data += chunk
        latex = data.decode("utf-8")
        print(f"\n✅ [TCP] 来自 {addr[0]}: {latex[:60]}...")
        to_clipboard(latex)
    except Exception as e:
        print(f"❌ TCP 错误: {e}")
    finally:
        conn.close()


# ========== 剪贴板 ==========
def to_clipboard(text):
    clean = text.replace("$$", "").replace("\\[", "").replace("\\]", "").strip()
    print(f"📋 剪贴板: {clean}")
    try:
        if sys.platform == "win32":
            subprocess.run(["clip"], input=clean.encode("utf-16le"), check=False)
        elif sys.platform == "darwin":
            subprocess.run(["pbcopy"], input=clean.encode("utf-8"), check=False)
        elif sys.platform == "linux":
            subprocess.run(["xclip", "-selection", "clipboard"],
                         input=clean.encode("utf-8"), check=False)
    except:
        print("⚠️ 剪贴板写入失败")


if __name__ == "__main__":
    local_ip = get_local_ip()
    print(f"""
╔══════════════════════════════════════╗
║     NoScrible PC Server v3             ║
║     ────────────────                 ║
║  平板打开: http://{local_ip}:{PORT}          ║
║                                      ║
║  原生 APP: 连接 {local_ip}:{TCP_PORT} (TCP)    ║
║  Web 版:   浏览器打开上方地址          ║
╚══════════════════════════════════════╝
    """)

    threading.Thread(target=tcp_server, daemon=True).start()

    httpd = http.server.HTTPServer((HOST, PORT), Handler)
    print(f"🌐 HTTP 服务: http://{HOST}:{PORT}")
    print(f"   📱 平板浏览器打开: http://{local_ip}:{PORT}")
    print(f"   💻 本机浏览器打开: http://localhost:{PORT}\n")
    httpd.serve_forever()
