"""PixVerse Platform API client (Text/Image → Video)."""

from __future__ import annotations

import json
import os
import time
import uuid
from pathlib import Path
from typing import Any

import requests

ROOT = Path(__file__).resolve().parents[1]
SECRETS = ROOT / "data" / "jami-secrets.local.json"
BASE = "https://app-api.pixverse.ai/openapi/v2"

STATUS_OK = 1
STATUS_PENDING = 5
STATUS_MOD_FAILED = 7
STATUS_FAILED = 8


def api_key() -> str:
    key = os.environ.get("PIXVERSE_API_KEY", "").strip()
    if key:
        return key
    if SECRETS.is_file():
        try:
            data = json.loads(SECRETS.read_text(encoding="utf-8"))
            return str(data.get("pixverse_api_key") or "").strip()
        except (json.JSONDecodeError, OSError):
            pass
    return ""


def _trace_id() -> str:
    return str(uuid.uuid4())


def _headers(trace: str | None = None) -> dict[str, str]:
    key = api_key()
    if not key:
        raise RuntimeError("PIXVERSE_API_KEY fehlt – Key auf platform.pixverse.ai holen.")
    return {
        "API-KEY": key,
        "Ai-trace-id": trace or _trace_id(),
    }


def _check_resp(data: dict) -> Any:
    if int(data.get("ErrCode", -1)) != 0:
        raise RuntimeError(str(data.get("ErrMsg") or "PixVerse API Fehler"))
    return data.get("Resp")


def text_to_video(
    prompt: str,
    *,
    aspect_ratio: str = "9:16",
    duration: int = 5,
    model: str = "v5.5",
    quality: str = "540p",
    negative_prompt: str = "blurry, low quality, text, watermark",
    generate_audio: bool = False,
) -> int:
    body = {
        "prompt": prompt.strip()[:5000],
        "aspect_ratio": aspect_ratio,
        "duration": duration,
        "model": model,
        "quality": quality,
        "negative_prompt": negative_prompt,
        "generate_audio_switch": bool(generate_audio),
    }
    r = requests.post(
        f"{BASE}/video/text/generate",
        headers={**_headers(), "Content-Type": "application/json"},
        json=body,
        timeout=60,
    )
    r.raise_for_status()
    resp = _check_resp(r.json())
    vid = int(resp.get("video_id") or 0)
    if not vid:
        raise RuntimeError("Keine video_id erhalten")
    return vid


def upload_image(path: Path) -> int:
    with path.open("rb") as f:
        r = requests.post(
            f"{BASE}/image/upload",
            headers=_headers(),
            files={"image": (path.name, f, "application/octet-stream")},
            timeout=120,
        )
    r.raise_for_status()
    data = r.json()
    if int(data.get("ErrCode", -1)) != 0:
        raise RuntimeError(str(data.get("ErrMsg") or "Upload fehlgeschlagen"))
    resp = data.get("Resp") or {}
    img_id = int(resp.get("img_id") or 0)
    if not img_id:
        raise RuntimeError("Keine img_id erhalten")
    return img_id


def image_to_video(
    img_id: int,
    prompt: str,
    *,
    aspect_ratio: str = "9:16",
    duration: int = 5,
    model: str = "v5.5",
    quality: str = "540p",
    motion_mode: str = "normal",
    negative_prompt: str = "blurry, low quality",
) -> int:
    body = {
        "img_id": img_id,
        "prompt": prompt.strip()[:5000] or "gentle cinematic motion",
        "aspect_ratio": aspect_ratio,
        "duration": duration,
        "model": model,
        "quality": quality,
        "motion_mode": motion_mode,
        "negative_prompt": negative_prompt,
    }
    r = requests.post(
        f"{BASE}/video/img/generate",
        headers={**_headers(), "Content-Type": "application/json"},
        json=body,
        timeout=60,
    )
    r.raise_for_status()
    resp = _check_resp(r.json())
    vid = int(resp.get("video_id") or 0)
    if not vid:
        raise RuntimeError("Keine video_id erhalten")
    return vid


def get_video_status(video_id: int) -> dict:
    r = requests.get(
        f"{BASE}/video/result/{video_id}",
        headers=_headers(),
        timeout=30,
    )
    r.raise_for_status()
    resp = _check_resp(r.json()) or {}
    return {
        "video_id": video_id,
        "status": int(resp.get("status") or 0),
        "url": str(resp.get("url") or ""),
        "prompt": str(resp.get("prompt") or ""),
        "width": resp.get("outputWidth"),
        "height": resp.get("outputHeight"),
    }


def wait_for_video(
    video_id: int,
    *,
    timeout_sec: float = 600,
    poll_sec: float = 4.0,
) -> dict:
    deadline = time.time() + timeout_sec
    last: dict = {}
    while time.time() < deadline:
        last = get_video_status(video_id)
        st = last.get("status")
        if st == STATUS_OK:
            return last
        if st in (STATUS_MOD_FAILED, STATUS_FAILED):
            raise RuntimeError(f"Generierung fehlgeschlagen (Status {st})")
        time.sleep(poll_sec)
    raise TimeoutError(f"Timeout nach {timeout_sec}s – letzter Status: {last.get('status')}")


def download_video(url: str, dest: Path) -> Path:
    dest.parent.mkdir(parents=True, exist_ok=True)
    with requests.get(url, stream=True, timeout=180) as r:
        r.raise_for_status()
        with dest.open("wb") as f:
            for chunk in r.iter_content(65536):
                if chunk:
                    f.write(chunk)
    return dest
