import argparse
import json
import os
from pathlib import Path
import tempfile
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


ENDPOINT = "https://api.elevenlabs.io/v1/music?output_format=mp3_44100_128"


def validate_request(payload):
    if not isinstance(payload, dict):
        raise ValueError("Request must be a JSON object")
    prompt = payload.get("prompt")
    if not isinstance(prompt, str) or not prompt.strip() or len(prompt) > 4100:
        raise ValueError("prompt must contain 1–4100 characters")
    duration = payload.get("music_length_ms")
    if type(duration) is not int or not 3000 <= duration <= 600000:
        raise ValueError("music_length_ms must be an integer from 3000 to 600000")
    if payload.get("force_instrumental") is not True:
        raise ValueError("force_instrumental must be true for this background cue")
    if not isinstance(payload.get("model_id"), str) or not payload["model_id"].strip():
        raise ValueError("model_id is required")
    if set(payload) != {"prompt", "music_length_ms", "model_id", "force_instrumental"}:
        raise ValueError("Use only prompt, music_length_ms, model_id and force_instrumental")
    return payload


def generate(payload, output, api_key):
    if output.exists():
        raise ValueError("Output already exists; choose a new filename")
    request = Request(ENDPOINT, data=json.dumps(payload).encode(), headers={
        "xi-api-key": api_key, "Content-Type": "application/json",
        "Accept": "audio/mpeg",
    })
    temporary = None
    try:
        with urlopen(request, timeout=600) as response:
            if response.headers.get_content_type() not in {"audio/mpeg", "audio/mp3"}:
                raise ValueError("The service returned a non-MP3 response; no audio was saved")
            output.parent.mkdir(parents=True, exist_ok=True)
            with tempfile.NamedTemporaryFile(dir=output.parent, delete=False) as stream:
                temporary = Path(stream.name)
                first = response.read(65536)
                if not (first.startswith(b"ID3") or (len(first) > 1 and first[0] == 255 and first[1] & 224 == 224)):
                    raise ValueError("Response does not begin with an MP3 header; no audio was saved")
                stream.write(first)
                while chunk := response.read(65536):
                    stream.write(chunk)
            if temporary.stat().st_size < 1024:
                raise ValueError("Audio response is unexpectedly short; no audio was saved")
            os.link(temporary, output)
    finally:
        if temporary is not None:
            temporary.unlink(missing_ok=True)


def main():
    parser = argparse.ArgumentParser(description="Generate one instrumental cue. A real run uses ElevenLabs credits.")
    parser.add_argument("--request", required=True, type=Path)
    parser.add_argument("--output", type=Path, default=Path("background-music.mp3"))
    parser.add_argument("--dry-run", action="store_true", help="Validate and print the request without a key or network call")
    args = parser.parse_args()
    try:
        payload = validate_request(json.loads(args.request.read_text()))
        if args.dry_run:
            print(json.dumps(payload, indent=2))
            return
        api_key = os.environ.get("ELEVENLABS_API_KEY")
        if not api_key:
            parser.error("ELEVENLABS_API_KEY is missing. Run with-secrets ai-dev python3 …")
        generate(payload, args.output, api_key)
    except HTTPError as error:
        parser.exit(1, f"ElevenLabs returned HTTP {error.code}; no output file was saved. Check account access and credits.\n")
    except URLError:
        parser.exit(1, "Network request failed. Check the service before retrying; a generation may have been charged.\n")
    except (ValueError, OSError) as error:
        parser.exit(1, f"{error}\n")
    print(f"Saved {args.output}. Audition it with the real narration before use.")


if __name__ == "__main__":
    main()
