"""Persistent voice profile storage.

Each voice profile is stored as a directory under VOICES_DIR:
    voices/<name>/
        prompt.pt        — Serialized voice_clone_prompt (torch tensors)
        reference.wav    — Original reference audio
        metadata.json    — {name, type, language, description, ref_text, created_at}
"""

from __future__ import annotations

import json
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import numpy as np
import soundfile as sf
import torch

VOICES_DIR = Path("/data/voices")


def _voice_dir(name: str) -> Path:
    return VOICES_DIR / name


def exists(name: str) -> bool:
    return _voice_dir(name).is_dir()


def list_voices() -> list[dict[str, str]]:
    if not VOICES_DIR.exists():
        return []
    results = []
    for d in sorted(VOICES_DIR.iterdir()):
        meta_path = d / "metadata.json"
        if d.is_dir() and meta_path.exists():
            results.append(json.loads(meta_path.read_text()))
    return results


def load_prompt(name: str) -> Any:
    """Load the saved voice_clone_prompt for reuse with generate_voice_clone()."""
    path = _voice_dir(name) / "prompt.pt"
    if not path.exists():
        raise FileNotFoundError(f"Voice profile '{name}' has no saved prompt")
    return torch.load(path, map_location="cpu", weights_only=False)


def get_reference_audio_path(name: str) -> Path:
    path = _voice_dir(name) / "reference.wav"
    if not path.exists():
        raise FileNotFoundError(f"Voice profile '{name}' has no reference audio")
    return path


def get_metadata(name: str) -> dict[str, str]:
    path = _voice_dir(name) / "metadata.json"
    if not path.exists():
        raise FileNotFoundError(f"Voice profile '{name}' not found")
    return json.loads(path.read_text())


def save_clone_profile(
    name: str,
    ref_audio: np.ndarray,
    sample_rate: int,
    ref_text: str,
    prompt: Any,
    language: str = "Auto",
) -> None:
    """Save a voice profile created by cloning from audio."""
    d = _voice_dir(name)
    d.mkdir(parents=True, exist_ok=True)

    sf.write(str(d / "reference.wav"), ref_audio, sample_rate)
    torch.save(prompt, str(d / "prompt.pt"))

    meta = {
        "name": name,
        "type": "clone",
        "language": language,
        "description": f"Cloned from audio ({len(ref_audio) / sample_rate:.1f}s)",
        "ref_text": ref_text,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    (d / "metadata.json").write_text(json.dumps(meta, indent=2))


def save_design_profile(
    name: str,
    ref_audio: np.ndarray,
    sample_rate: int,
    ref_text: str,
    prompt: Any,
    instruct: str,
    language: str = "English",
) -> None:
    """Save a voice profile created by designing from a text description."""
    d = _voice_dir(name)
    d.mkdir(parents=True, exist_ok=True)

    sf.write(str(d / "reference.wav"), ref_audio, sample_rate)
    torch.save(prompt, str(d / "prompt.pt"))

    meta = {
        "name": name,
        "type": "design",
        "language": language,
        "description": instruct,
        "ref_text": ref_text,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    (d / "metadata.json").write_text(json.dumps(meta, indent=2))


def delete_voice(name: str) -> None:
    d = _voice_dir(name)
    if d.exists():
        shutil.rmtree(d)
