#!/usr/bin/env python3
"""Create resumable word-timestamp ASR sidecars for audiobook tracks."""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
import time
from pathlib import Path


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as source:
        for block in iter(lambda: source.read(1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()


def serialize_segment(segment) -> dict[str, object]:
    return {
        "id": segment.id,
        "start": round(segment.start, 3),
        "end": round(segment.end, 3),
        "text": segment.text.strip(),
        "avg_logprob": round(segment.avg_logprob, 5),
        "compression_ratio": round(segment.compression_ratio, 5),
        "no_speech_prob": round(segment.no_speech_prob, 5),
        "words": [
            {
                "start": round(word.start, 3),
                "end": round(word.end, 3),
                "word": word.word,
                "probability": round(word.probability, 5),
            }
            for word in (segment.words or ())
        ],
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--source-dir", type=Path, required=True)
    parser.add_argument("--output-dir", type=Path, required=True)
    parser.add_argument("--model", default="base")
    parser.add_argument("--model-cache", type=Path, required=True)
    parser.add_argument("--max-files", type=int)
    parser.add_argument("--force", action="store_true")
    args = parser.parse_args()

    try:
        from faster_whisper import WhisperModel
    except ImportError as error:
        raise SystemExit("faster-whisper is missing from PYTHONPATH") from error

    files = sorted(args.source_dir.glob("*.mp3"))
    if args.max_files is not None:
        files = files[: max(0, args.max_files)]
    if not files:
        parser.error("No MP3 files found")
    args.output_dir.mkdir(parents=True, exist_ok=True)
    model = WhisperModel(
        args.model,
        device="cpu",
        compute_type="int8",
        download_root=str(args.model_cache),
        local_files_only=True,
    )

    completed = 0
    for index, audio_path in enumerate(files, 1):
        output_path = args.output_dir / f"{audio_path.stem[:4]}.json"
        if output_path.is_file() and not args.force:
            print(f"[{index}/{len(files)}] skip {audio_path.name}", flush=True)
            completed += 1
            continue
        started = time.monotonic()
        print(f"[{index}/{len(files)}] transcribe {audio_path.name}", flush=True)
        segments_iter, info = model.transcribe(
            str(audio_path),
            language="ru",
            beam_size=5,
            vad_filter=True,
            word_timestamps=True,
            condition_on_previous_text=True,
        )
        segments = [serialize_segment(segment) for segment in segments_iter]
        payload = {
            "schema": 1,
            "source": str(audio_path.resolve()),
            "source_bytes": audio_path.stat().st_size,
            "source_sha256": sha256(audio_path),
            "language": info.language,
            "language_probability": round(info.language_probability, 5),
            "duration": round(info.duration, 3),
            "duration_after_vad": round(info.duration_after_vad, 3),
            "elapsed_seconds": round(time.monotonic() - started, 3),
            "segments": segments,
        }
        temporary = output_path.with_suffix(".json.tmp")
        temporary.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
        temporary.replace(output_path)
        completed += 1
        print(
            f"[{index}/{len(files)}] wrote {output_path.name}: "
            f"{len(segments)} segments in {payload['elapsed_seconds']}s",
            flush=True,
        )
    print(json.dumps({"files": len(files), "completed": completed}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    sys.exit(main())
