#!/usr/bin/env python3
"""作业陪伴助手的轻量服务器（纯 Python 标准库，适合老旧 / 小内存 Linux，无需 pip 安装）：
   - 从 .env 读取密钥（都不进浏览器）
   - 提供静态文件（homework.html 等）
   - /api/chat  把对话转发给 DeepSeek
   - /api/ocr   把作业照片转发给百度 OCR 认成文字（算力在百度云端）
   运行： python3 server.py   然后打开 http://localhost:8000/homework.html
"""
import http.server, socketserver, json, os, time, ssl, urllib.request, urllib.error, urllib.parse

PORT = 8000

def urlopen_safe(req, timeout):
    """正常做 TLS 校验；只有当系统证书库缺失/损坏（如某些 mac/极简系统）时才回退为不校验。
       正常配置好的 Debian 服务器始终走校验路径。"""
    try:
        return urllib.request.urlopen(req, timeout=timeout)
    except urllib.error.URLError as e:
        if isinstance(getattr(e, "reason", None), ssl.SSLError):
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            return urllib.request.urlopen(req, timeout=timeout, context=ctx)
        raise

def load_env(path=".env"):
    env = {}
    try:
        with open(path, encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                k, v = line.split("=", 1)
                env[k.strip()] = v.strip().strip('"').strip("'")
    except FileNotFoundError:
        pass
    return env

ENV = load_env()
KEY = ENV.get("DEEPSEEK_API_KEY", "") or os.environ.get("DEEPSEEK_API_KEY", "")
BAIDU_API_KEY = ENV.get("BAIDU_API_KEY", "")
BAIDU_SECRET_KEY = ENV.get("BAIDU_SECRET_KEY", "")
# 语音合成可用单独的应用密钥；没填就回退用 OCR 那对
BAIDU_TTS_API_KEY = ENV.get("BAIDU_TTS_API_KEY", "") or BAIDU_API_KEY
BAIDU_TTS_SECRET_KEY = ENV.get("BAIDU_TTS_SECRET_KEY", "") or BAIDU_SECRET_KEY
# 百度语音合成（真人音）。发音人 per: 0度小美/1度小宇/3度逍遥/4度丫丫(童声)
TTS_PER = ENV.get("TTS_VOICE", "0")
TTS_SPD = ENV.get("TTS_SPEED", "5")
TTS_PIT = ENV.get("TTS_PITCH", "5")
TTS_VOL = ENV.get("TTS_VOL", "6")

# 百度 access_token 缓存（按 api_key 分别缓存，有效期约 30 天）
_tok_cache = {}

def baidu_token(ak, sk):
    now = time.time()
    c = _tok_cache.get(ak)
    if c and c["exp"] > now + 60:
        return c["token"]
    url = ("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials"
           "&client_id=%s&client_secret=%s" % (urllib.parse.quote(ak), urllib.parse.quote(sk)))
    req = urllib.request.Request(url, data=b"", headers={"content-type": "application/x-www-form-urlencoded"})
    with urlopen_safe(req, 30) as r:
        d = json.loads(r.read())
    tok = d.get("access_token", "")
    if not tok:
        raise Exception("百度 token 获取失败：" + json.dumps(d)[:200])
    _tok_cache[ak] = {"token": tok, "exp": now + int(d.get("expires_in", 2592000))}
    return tok

class Handler(http.server.SimpleHTTPRequestHandler):
    def _json(self, code, obj):
        body = json.dumps(obj).encode("utf-8")
        self.send_response(code)
        self.send_header("content-type", "application/json")
        self.send_header("content-length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _read_json(self):
        length = int(self.headers.get("content-length", 0))
        return json.loads(self.rfile.read(length) or b"{}")

    def do_POST(self):
        if self.path == "/api/chat":
            return self.handle_chat()
        if self.path == "/api/ocr":
            return self.handle_ocr()
        if self.path == "/api/tts":
            return self.handle_tts()
        self.send_response(404); self.end_headers()

    # ---- DeepSeek 对话转发 ----
    def handle_chat(self):
        if not KEY or KEY.startswith("sk-REPLACE"):
            return self._json(500, {"error": ".env 里还没有填入真正的 DEEPSEEK_API_KEY"})
        try:
            payload = self._read_json()
            payload.setdefault("model", "deepseek-chat")
            req = urllib.request.Request(
                "https://api.deepseek.com/chat/completions",
                data=json.dumps(payload).encode("utf-8"),
                headers={"content-type": "application/json", "authorization": "Bearer " + KEY},
            )
            with urlopen_safe(req, 90) as r:
                data = r.read()
            self.send_response(200)
            self.send_header("content-type", "application/json")
            self.send_header("content-length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
        except urllib.error.HTTPError as e:
            self._json(e.code, {"error": "DeepSeek 返回错误：" + e.read().decode("utf-8", "ignore")[:300]})
        except Exception as e:
            self._json(500, {"error": str(e)})

    # ---- 百度 OCR 认题 ----
    def handle_ocr(self):
        if not (BAIDU_API_KEY and BAIDU_SECRET_KEY):
            return self._json(200, {"text": "", "error": "baidu_not_configured"})
        try:
            img = self._read_json().get("image", "")
            if "," in img:                       # 去掉 data:image/...;base64, 前缀
                img = img.split(",", 1)[1]
            tok = baidu_token(BAIDU_API_KEY, BAIDU_SECRET_KEY)
            body = urllib.parse.urlencode({"image": img}).encode("utf-8")
            req = urllib.request.Request(
                "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=" + tok,
                data=body, headers={"content-type": "application/x-www-form-urlencoded"},
            )
            with urlopen_safe(req, 30) as r:
                d = json.loads(r.read())
            if "words_result" in d:
                text = "\n".join(w.get("words", "") for w in d["words_result"]).strip()
                return self._json(200, {"text": text, "via": "baidu"})
            return self._json(200, {"text": "", "error": str(d.get("error_msg", "ocr_failed")) + "(" + str(d.get("error_code", "")) + ")"})
        except Exception as e:
            return self._json(200, {"text": "", "error": str(e)})

    # ---- 百度语音合成（真人音）----
    def handle_tts(self):
        if not (BAIDU_API_KEY and BAIDU_SECRET_KEY):
            return self._json(200, {"error": "baidu_not_configured"})
        try:
            text = (self._read_json().get("text", "") or "").strip()[:200]
            if not text:
                return self._json(400, {"error": "empty"})
            tok = baidu_token(BAIDU_TTS_API_KEY, BAIDU_TTS_SECRET_KEY)
            params = urllib.parse.urlencode({
                "tex": text, "tok": tok, "cuid": "trinitye", "ctp": "1", "lan": "zh",
                "spd": TTS_SPD, "pit": TTS_PIT, "vol": TTS_VOL, "per": TTS_PER, "aue": "3",
            }).encode("utf-8")
            req = urllib.request.Request(
                "https://tsn.baidu.com/text2audio",
                data=params, headers={"content-type": "application/x-www-form-urlencoded"},
            )
            with urlopen_safe(req, 30) as r:
                ctype = r.headers.get("Content-Type", "")
                body = r.read()
            if "audio" in ctype:
                self.send_response(200)
                self.send_header("content-type", "audio/mp3")
                self.send_header("content-length", str(len(body)))
                self.end_headers()
                self.wfile.write(body)
            else:                          # 失败时百度返回 JSON 错误，前端会回退浏览器语音
                return self._json(200, {"error": body.decode("utf-8", "ignore")[:200]})
        except Exception as e:
            return self._json(200, {"error": str(e)})

    def log_message(self, *a):
        pass

socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("✅ 服务器已启动： http://localhost:%d/homework.html" % PORT)
    print("🔑 DeepSeek 密钥：" + ("已读到" if KEY and not KEY.startswith("sk-REPLACE") else "⚠️ 未配置"))
    print("👁️  百度 OCR：" + ("已配置" if (BAIDU_API_KEY and BAIDU_SECRET_KEY) else "未配置（将回退浏览器 Tesseract）"))
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print("\n已停止。")
