#!/usr/bin/env python3
import hashlib
import json
import os
import subprocess
import sys
import urllib.parse
import urllib.request
import wave
from datetime import datetime, timezone
from pathlib import Path

PROJECT = Path(__file__).resolve().parents[1]
PROMOTION = PROJECT / "story/promotion-report.json"
SOURCE = PROJECT / "audio/intro-voice-source.txt"
PROJECTION = PROJECT / "audio/intro-voice-pronunciation.txt"
PRONUNCIATION = PROJECT / "log/intro-tts-pronunciation.json"
LEXICON = PROJECT / "script/pronunciation-lexicon.json"
VOICE = PROJECT / "audio/intro-voice.wav"
FULL = PROJECT / "audio/intro-full.wav"
RECEIPT = PROJECT / "log/intro-voice.json"
BASE = "http://192.168.40.33:7862"
VOICE_NAME = "ngoc-huyen-vbee"


def sha(path):
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(8 * 1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()


def wav_info(path):
    with wave.open(str(path), "rb") as handle:
        return {"channels": handle.getnchannels(), "sample_width": handle.getsampwidth(), "sample_rate": handle.getframerate(), "frames": handle.getnframes(), "compression": handle.getcomptype(), "duration_seconds": handle.getnframes() / handle.getframerate()}


def get_json(path):
    with urllib.request.urlopen(BASE + path, timeout=20) as response:
        return json.load(response)


def atomic_json(path, value):
    temp = path.with_name(path.name + ".part")
    temp.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    os.replace(temp, path)


def main():
    for path in (PROMOTION, SOURCE, PROJECTION, PRONUNCIATION, LEXICON):
        if not path.is_file():
            raise RuntimeError(f"missing {path}")
    promotion = json.loads(PROMOTION.read_text(encoding="utf-8"))
    pronunciation = json.loads(PRONUNCIATION.read_text(encoding="utf-8"))
    canon = promotion.get("canon_sha256")
    checks = (
        promotion.get("verified") is True,
        pronunciation.get("verified") is True,
        pronunciation.get("kind") == "intro",
        pronunciation.get("source_canon_sha256") == canon,
        pronunciation.get("source_sha256") == sha(SOURCE),
        pronunciation.get("projection_sha256") == sha(PROJECTION),
        pronunciation.get("lexicon_sha256") == sha(LEXICON),
        pronunciation.get("reverse_verified") is True,
        pronunciation.get("semantic_content_changed") is False,
        not pronunciation.get("unresolved_required"),
    )
    if not all(checks):
        raise RuntimeError("intro pronunciation authority mismatch")
    if RECEIPT.exists() and VOICE.exists() and FULL.exists():
        old = json.loads(RECEIPT.read_text(encoding="utf-8"))
        if old.get("verified") is True and old.get("status") == "completed" and old.get("source_canon_sha256") == canon and old.get("projection_sha256") == sha(PROJECTION) and old.get("output_sha256") == sha(VOICE) and old.get("full_output_sha256") == sha(FULL):
            print(json.dumps({"verified": True, "reused": True, "duration_seconds": old["full_wav"]["duration_seconds"]}, ensure_ascii=False))
            return 0
    health = get_json("/health")
    voices = get_json("/voices").get("voices", [])
    row = next((item for item in voices if item.get("id") == VOICE_NAME), None)
    if health.get("status") != "ok" or not row or row.get("model_exists") is not True or row.get("config_exists") is not True:
        raise RuntimeError("intro TTS capability gate failed")
    body = urllib.parse.urlencode({"text": PROJECTION.read_text(encoding="utf-8"), "voice": VOICE_NAME}).encode("utf-8")
    request = urllib.request.Request(BASE + "/tts", data=body, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
    with urllib.request.urlopen(request, timeout=300) as response:
        data = response.read()
    if len(data) < 44 or data[:4] != b"RIFF" or data[8:12] != b"WAVE":
        raise RuntimeError("intro TTS response is not WAV")
    VOICE.parent.mkdir(parents=True, exist_ok=True)
    temp = VOICE.with_suffix(".part.wav")
    temp.write_bytes(data)
    info = wav_info(temp)
    if info["duration_seconds"] <= 0 or info["compression"] != "NONE":
        temp.unlink(missing_ok=True)
        raise RuntimeError("intro voice WAV verification failed")
    os.replace(temp, VOICE)
    with wave.open(str(VOICE), "rb") as source:
        params = source.getparams()
        frames = source.readframes(source.getnframes())
    full_temp = FULL.with_suffix(".part.wav")
    silence = b"\x00" * int(params.framerate * 2.0) * params.nchannels * params.sampwidth
    with wave.open(str(full_temp), "wb") as target:
        target.setparams(params)
        target.writeframes(frames)
        target.writeframes(silence)
    os.replace(full_temp, FULL)
    full_info = wav_info(FULL)
    receipt = {"version": 1, "verified": True, "status": "completed", "source_canon_sha256": canon, "source_path": str(SOURCE.relative_to(PROJECT)), "source_sha256": sha(SOURCE), "projection_path": str(PROJECTION.relative_to(PROJECT)), "projection_sha256": sha(PROJECTION), "pronunciation_receipt": str(PRONUNCIATION.relative_to(PROJECT)), "pronunciation_receipt_sha256": sha(PRONUNCIATION), "pronunciation_lexicon_sha256": sha(LEXICON), "voice": VOICE_NAME, "base_url": BASE, "output": str(VOICE.relative_to(PROJECT)), "output_sha256": sha(VOICE), "wav": info, "full_output": str(FULL.relative_to(PROJECT)), "full_output_sha256": sha(FULL), "full_wav": full_info, "silence_seconds": 2.0, "checked_at": datetime.now(timezone.utc).isoformat()}
    atomic_json(RECEIPT, receipt)
    print(json.dumps({"verified": True, "duration_seconds": full_info["duration_seconds"], "output_sha256": receipt["output_sha256"], "full_output_sha256": receipt["full_output_sha256"]}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as exc:
        print("Intro voice blocked: " + str(exc), file=sys.stderr)
        sys.exit(1)
