from pathlib import Path
import datetime
import hashlib
import json
import os
import subprocess
import time
import urllib.parse
import urllib.request
import wave

ROOT = Path(__file__).resolve().parents[1]
PLAN_PATH = ROOT / "script/tts-plan.json"
RECEIPT_PATH = ROOT / "log/tts.json"
ENDPOINT = "http://192.168.40.33:7862/tts"


def atomic_json(path, value):
    temporary = Path(str(path) + ".tmp")
    temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n")
    os.replace(temporary, path)


def sha256(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()


plan = json.loads(PLAN_PATH.read_text())
segments_dir = ROOT / "audio/segments"
segments_dir.mkdir(parents=True, exist_ok=True)
for segment in plan["segments"]:
    output = segments_dir / f"segment-{segment['index']:03d}.wav"
    segment["output_path"] = str(output)
    if output.exists() and output.stat().st_size > 44:
        try:
            with wave.open(str(output), "rb") as wav:
                duration = wav.getnframes() / wav.getframerate()
                assert duration > 0 and wav.getnchannels() == 1
            segment.update(status="completed", artifact_sha256=sha256(output), duration_seconds=duration)
            atomic_json(PLAN_PATH, plan)
            continue
        except Exception:
            raise RuntimeError(f"Existing segment {segment['index']} is invalid; reconcile manually")
    part = Path(str(output) + ".part")
    if part.exists():
        raise RuntimeError(f"Ambiguous partial artifact for segment {segment['index']}")
    body = urllib.parse.urlencode({"text": segment["text"], "voice": plan["voice"]}).encode()
    segment["request_sha256"] = hashlib.sha256(body).hexdigest()
    atomic_json(PLAN_PATH, plan)
    last_error = None
    for attempt in range(1, 4):
        try:
            request = urllib.request.Request(ENDPOINT, data=body, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
            with urllib.request.urlopen(request, timeout=600) as response:
                payload = response.read()
                content_type = response.headers.get("Content-Type", "")
            if payload[:4] != b"RIFF" or payload[8:12] != b"WAVE":
                raise RuntimeError("Provider returned non-WAV payload")
            part.write_bytes(payload)
            with wave.open(str(part), "rb") as wav:
                duration = wav.getnframes() / wav.getframerate()
                rate = wav.getframerate()
                channels = wav.getnchannels()
                assert duration > 0 and channels == 1
            os.replace(part, output)
            segment.update(status="completed", artifact_sha256=sha256(output), duration_seconds=duration, sample_rate=rate, channels=channels, attempts=attempt, content_type=content_type)
            atomic_json(PLAN_PATH, plan)
            break
        except Exception as error:
            last_error = str(error)
            segment.update(status="retrying", attempts=attempt, last_error=last_error)
            atomic_json(PLAN_PATH, plan)
            if part.exists():
                raise RuntimeError(f"Ambiguous partial artifact for segment {segment['index']}: {last_error}")
            if attempt < 3:
                time.sleep(2**attempt)
    else:
        segment.update(status="failed", last_error=last_error)
        atomic_json(PLAN_PATH, plan)
        raise RuntimeError(f"Segment {segment['index']} failed: {last_error}")

concat = segments_dir / "concat.txt"
concat.write_text("".join(f"file '{segment['output_path']}'\n" for segment in plan["segments"]))
final = ROOT / "audio/story-full.wav"
part_final = Path(str(final) + ".part.wav")
subprocess.run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat), "-c:a", "pcm_s16le", str(part_final)], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
probe = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-show_entries", "stream=index,codec_type,codec_name,sample_rate,channels", "-of", "json", str(part_final)]))
os.replace(part_final, final)
receipt = {"status": "completed", "verified": True, "provider": "piper-wrapper", "base_url": "http://192.168.40.33:7862", "endpoint": "/tts", "voice": plan["voice"], "input_path": plan["source_path"], "input_text_sha256": plan["source_sha256"], "segment_count": len(plan["segments"]), "output_path": str(final), "artifact_sha256": sha256(final), "artifact_bytes": final.stat().st_size, "duration_seconds": float(probe["format"]["duration"]), "probe": probe, "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat()}
atomic_json(RECEIPT_PATH, receipt)
print(json.dumps(receipt, ensure_ascii=False))
