#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""新思维2A工作台语音服务：静态文件 + /tts发音 + /asr识别 + /report上报 + /photo拍照上传"""
import http.server, socketserver, subprocess, tempfile, os, json, urllib.parse, datetime, base64

ROOT = "/Users/zhangwen/.local/share/TeleAgent/TeleAgent的工作空间/学习工作台"
WHISPER = ROOT + "/.temp/whisper-cpp/build/bin/whisper-cli"
MODEL = ROOT + "/.temp/whisper-model/ggml-base.en.bin"
REPORT_DIR = ROOT + "/reports"
PHOTO_DIR = ROOT + "/photos"

def ensure_report_dir():
    os.makedirs(REPORT_DIR, exist_ok=True)

def ensure_photo_dir():
    os.makedirs(PHOTO_DIR, exist_ok=True)

def load_report(date):
    ensure_report_dir()
    p = os.path.join(REPORT_DIR, date + ".json")
    if os.path.exists(p):
        try:
            with open(p, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            return {"date": date, "subjects": {}}
    return {"date": date, "subjects": {}}

def save_report(date, data):
    ensure_report_dir()
    p = os.path.join(REPORT_DIR, date + ".json")
    with open(p, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def mac_tts_to_wav(text, lang="en"):
    """用 macOS say + afconvert 生成 16kHz wav，返回字节；lang='zh' 用中文语音"""
    with tempfile.TemporaryDirectory() as td:
        aiff = os.path.join(td, "t.aiff")
        wav = os.path.join(td, "t.wav")
        voice = "Tingting" if lang == "zh" else "Samantha"
        subprocess.run(["say", "-o", aiff, "-v", voice, text[:300]],
                       check=True, timeout=20,
                       stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
        subprocess.run(["afconvert", "-f", "WAVE", "-d", "LEI16@16000", "-c", "1", aiff, wav],
                       check=True, timeout=20)
        with open(wav, "rb") as f:
            return f.read()

def whisper_asr(wav_bytes):
    """whisper-cli 转写，返回文本（先统一转 16kHz 单声道，兼容手机任意采样率）"""
    with tempfile.TemporaryDirectory() as td:
        wav = os.path.join(td, "a.wav")
        out = os.path.join(td, "b.wav")
        with open(wav, "wb") as f:
            f.write(wav_bytes)
        try:
            subprocess.run(["afconvert", "-f", "WAVE", "-d", "LEI16@16000", "-c", "1", wav, out],
                           check=True, timeout=20,
                           stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
            wav = out
        except Exception:
            pass  # 转换失败就用原始文件
        r = subprocess.run(
            [WHISPER, "-m", MODEL, "-f", wav, "-nt", "-np", "--no-prints"],
            capture_output=True, text=True, timeout=60)
        return (r.stdout or "").strip()

class Handler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *a, **kw):
        super().__init__(*a, directory=ROOT, **kw)

    def log_message(self, fmt, *args):
        pass  # 静默访问日志

    def end_headers(self):
        self.send_header("Cross-Origin-Opener-Policy", "same-origin")
        self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
        super().end_headers()

    def _send(self, code, ctype, data):
        self.send_response(code)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(data)))
        self.send_header("Cache-Control", "no-store")
        self.end_headers()
        self.wfile.write(data)

    def do_GET(self):
        try:
            parsed = urllib.parse.urlparse(self.path)
            if parsed.path == "/tts":
                q = urllib.parse.parse_qs(parsed.query)
                text = (q.get("text") or [""])[0].strip()
                lang = (q.get("lang") or ["en"])[0].strip() or "en"
                if not text:
                    self._send(400, "text/plain", b"missing text")
                    return
                data = mac_tts_to_wav(text, lang)
                self._send(200, "audio/wav", data)
                return
        except Exception as e:
            self._send(500, "application/json", json.dumps({"error": str(e)}).encode())
            return
        super().do_GET()

    def do_POST(self):
        if self.path == "/report":
            self._handle_report()
            return
        if self.path == "/photo":
            self._handle_photo()
            return
        if self.path != "/asr":
            self._send(404, "text/plain", b"not found")
            return
        try:
            length = int(self.headers.get("Content-Length") or 0)
            if length <= 0 or length > 20 * 1024 * 1024:
                self._send(400, "text/plain", b"bad size")
                return
            data = self.rfile.read(length)
            text = whisper_asr(data)
            self._send(200, "application/json", json.dumps({"text": text}).encode())
        except Exception as e:
            try:
                self._send(200, "application/json", json.dumps({"error": str(e)}).encode())
            except Exception:
                pass

    def _handle_photo(self):
        """语文当日练习拍照上传：{base64, unit, date} → 存 Mac photos/ + 企微推送"""
        try:
            length = int(self.headers.get("Content-Length") or 0)
            if length <= 0 or length > 15 * 1024 * 1024:
                self._send(400, "text/plain", b"bad size")
                return
            raw = self.rfile.read(length)
            payload = json.loads(raw.decode("utf-8"))
            b64 = payload.get("base64", "")
            if not b64:
                self._send(400, "application/json", json.dumps({"error": "no image"}).encode())
                return
            img = base64.b64decode(b64)
            date = payload.get("date", datetime.date.today().isoformat())
            unit = payload.get("unit", "")
            ensure_photo_dir()
            ddir = os.path.join(PHOTO_DIR, date)
            os.makedirs(ddir, exist_ok=True)
            fname = datetime.datetime.now().strftime("%H%M%S") + "_" + (unit or "yu") + ".jpg"
            fpath = os.path.join(ddir, fname)
            with open(fpath, "wb") as f:
                f.write(img)
            # 企微推送（后台异步，不阻塞返回）
            try:
                subprocess.Popen(["python3", os.path.join(ROOT, ".temp", "push_photo.py"),
                                  fpath, date, unit],
                                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            except Exception:
                pass
            self._send(200, "application/json", json.dumps({"ok": True, "path": fpath}).encode())
        except Exception as e:
            try:
                self._send(200, "application/json", json.dumps({"error": str(e)}).encode())
            except Exception:
                pass

    def _handle_report(self):
        """工作台打卡上报：{subject:'en|yu|ma', date:'YYYY-MM-DD', summary:{...}}"""
        try:
            length = int(self.headers.get("Content-Length") or 0)
            if length <= 0 or length > 2 * 1024 * 1024:
                self._send(400, "text/plain", b"bad size")
                return
            raw = self.rfile.read(length)
            payload = json.loads(raw.decode("utf-8"))
            subject = payload.get("subject", "")
            date = payload.get("date", datetime.date.today().isoformat())
            summary = payload.get("summary") or {}
            if subject not in ("en", "yu", "sh") or not isinstance(summary, dict):
                self._send(400, "application/json", json.dumps({"error": "bad payload"}).encode())
                return
            rep = load_report(date)
            rep["subjects"][subject] = summary
            save_report(date, rep)
            self._send(200, "application/json", json.dumps({"ok": True}).encode())
        except Exception as e:
            try:
                self._send(200, "application/json", json.dumps({"error": str(e)}).encode())
            except Exception:
                pass

class Server(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = True

if __name__ == "__main__":
    print("voice server on :8000")
    Server(("0.0.0.0", 8000), Handler).serve_forever()
