"""FikoYT RVC Voice Clone API Sunucusu
==========================================
- FastAPI Uvicorn port 8004
- Endpoints:
  - GET  /health           → ping + RVC kurulu mu
  - POST /clone/baslat     → ses klonlama görevi başlat → {is_id}
  - GET  /clone/job/{id}   → görev durumu
  - POST /upload           → ses dosyası yükle
  - GET  /models           → mevcut RVC modellerini listele
- RVC kurulu değilse stub davranış: hata mesajıyla yanıt verir, sunucu çöker durmaz.
"""
from __future__ import annotations

# Windows console UTF-8 fix
import sys as _sys
try:
    _sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    _sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass

import argparse
import asyncio
import json
import math
import shutil
import subprocess
import sys
import threading
import time
import traceback
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional

from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel, Field


def _json_temizle(obj):
    """NaN/Inf -> None (JSON uyumlu)."""
    if isinstance(obj, float):
        if math.isnan(obj) or math.isinf(obj):
            return None
        return obj
    if isinstance(obj, dict):
        return {k: _json_temizle(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_json_temizle(v) for v in obj]
    return obj


# ===================== RVC Path Tespiti =====================
RVC_OLASI_PATHLER = [
    Path("D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/video-uretim/Retrieval-based-Voice-Conversion-WebUI"),
    Path("D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/video-uretim/RVC"),
    Path("D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/video-uretim/rvc-webui"),
]
RVC_PATH: Optional[Path] = next((p for p in RVC_OLASI_PATHLER if p.exists()), None)

SUNUCU_KOK = Path(__file__).parent.resolve()
CIKTI_KOK = SUNUCU_KOK / "ciktilar"
YUKLEME_KOK = SUNUCU_KOK / "yuklemeler"
MODELLER_KOK = SUNUCU_KOK / "modeller"  # Import edilen .pth + .index
CIKTI_KOK.mkdir(parents=True, exist_ok=True)
YUKLEME_KOK.mkdir(parents=True, exist_ok=True)
MODELLER_KOK.mkdir(parents=True, exist_ok=True)


def rvc_python_yolu() -> Optional[Path]:
    """RVC sanal ortamının python.exe yolunu döner."""
    if RVC_PATH is None:
        return None
    olasiliklar = [
        RVC_PATH / ".venv" / "Scripts" / "python.exe",
        RVC_PATH / "venv" / "Scripts" / "python.exe",
        RVC_PATH / "runtime" / "python.exe",
    ]
    for p in olasiliklar:
        if p.exists():
            return p
    return None


# ===================== Modeller =====================
class CloneIstegi(BaseModel):
    kaynak_ses: str = Field(..., description="Yerel ses dosyasının tam yolu")
    model_yolu: str = Field(..., description="RVC .pth model dosyası")
    index_yolu: Optional[str] = Field(None, description="RVC .index dosyası (opsiyonel)")
    transpoze: int = Field(0, description="Yarı ton kaydırma (-12..+12)")


class CloneCevap(BaseModel):
    basarili: bool
    is_id: Optional[str] = None
    hata: Optional[str] = None


class IsDurumu(BaseModel):
    is_id: str
    durum: int  # 0=calisiyor, 1=tamam, 2=hata
    ilerleme: int = 0
    cikti_yolu: Optional[str] = None
    hata: Optional[str] = None
    baslama: float
    bitis: Optional[float] = None


# ===================== İş Yönetimi =====================
_isler: Dict[str, IsDurumu] = {}
_kilit = threading.Lock()


def _is_calistir(is_id: str, istek: CloneIstegi):
    """Arka planda RVC çıkarımı yapar."""
    with _kilit:
        durum = _isler[is_id]

    try:
        py = rvc_python_yolu()
        if py is None or RVC_PATH is None:
            raise RuntimeError("RVC kurulu değil — RVC_PATH bulunamadı")

        kaynak = Path(istek.kaynak_ses)
        if not kaynak.exists():
            raise FileNotFoundError(f"Kaynak ses bulunamadı: {kaynak}")

        cikti = CIKTI_KOK / f"clone_{is_id}.wav"

        # Birkaç olası RVC infer-cli script ismi dene
        infer_scriptleri = [
            RVC_PATH / "infer-cli.py",
            RVC_PATH / "infer_cli.py",
            RVC_PATH / "tools" / "infer_cli.py",
            RVC_PATH / "tools" / "infer-cli.py",
        ]
        infer_script = next((s for s in infer_scriptleri if s.exists()), None)
        if infer_script is None:
            raise RuntimeError(
                f"RVC infer-cli script bulunamadı (denenen yollar: "
                f"{[str(s) for s in infer_scriptleri]})"
            )

        cmd: List[str] = [
            str(py),
            str(infer_script),
            "--input", str(kaynak),
            "--model", str(istek.model_yolu),
            "--output", str(cikti),
            "--f0_up_key", str(istek.transpoze),
        ]
        if istek.index_yolu:
            cmd.extend(["--index", str(istek.index_yolu)])

        with _kilit:
            durum.ilerleme = 10

        sonuc = subprocess.run(
            cmd,
            cwd=str(RVC_PATH),
            capture_output=True,
            text=True,
            encoding="utf-8",
            errors="replace",
            timeout=600,
        )
        if sonuc.returncode != 0:
            raise RuntimeError(
                f"RVC çıkarımı başarısız (kod {sonuc.returncode}): "
                f"{sonuc.stderr or sonuc.stdout}"
            )

        if not cikti.exists():
            raise RuntimeError(f"Çıktı dosyası oluşmadı: {cikti}")

        with _kilit:
            durum.durum = 1
            durum.ilerleme = 100
            durum.cikti_yolu = str(cikti)
            durum.bitis = time.time()

    except Exception as e:
        with _kilit:
            durum.durum = 2
            durum.hata = str(e)
            durum.bitis = time.time()


# ===================== FastAPI =====================
app = FastAPI(title="FikoYT RVC Voice Clone API", version="0.1.0")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/health")
def saglik():
    py = rvc_python_yolu()
    return {
        "basarili": True,
        "rvc_kurulu": RVC_PATH is not None and py is not None,
        "rvc_path": str(RVC_PATH) if RVC_PATH else None,
        "python_yolu": str(py) if py else None,
        "aktif_is": sum(1 for d in _isler.values() if d.durum == 0),
    }


@app.get("/models")
def model_listesi() -> Dict[str, Any]:
    """RVC kurulumundaki .pth modellerini listele."""
    if RVC_PATH is None:
        return {"basarili": False, "hata": "RVC kurulu değil", "modeller": []}

    olasi_dizinler = [
        RVC_PATH / "assets" / "weights",
        RVC_PATH / "weights",
        RVC_PATH / "models",
    ]
    modeller: List[Dict[str, str]] = []
    for d in olasi_dizinler:
        if not d.exists():
            continue
        for pth in d.rglob("*.pth"):
            modeller.append({
                "isim": pth.stem,
                "yol": str(pth),
                "boyut_mb": round(pth.stat().st_size / (1024 * 1024), 2),
            })
    return {"basarili": True, "modeller": modeller}


@app.post("/clone/baslat", response_model=CloneCevap)
def clone_baslat(istek: CloneIstegi) -> CloneCevap:
    if RVC_PATH is None or rvc_python_yolu() is None:
        return CloneCevap(basarili=False, hata="RVC kurulu değil")

    if not Path(istek.kaynak_ses).exists():
        return CloneCevap(basarili=False, hata=f"Kaynak ses yok: {istek.kaynak_ses}")
    if not Path(istek.model_yolu).exists():
        return CloneCevap(basarili=False, hata=f"Model yok: {istek.model_yolu}")

    is_id = uuid.uuid4().hex[:12]
    durum = IsDurumu(
        is_id=is_id,
        durum=0,
        ilerleme=0,
        baslama=time.time(),
    )
    with _kilit:
        _isler[is_id] = durum

    th = threading.Thread(target=_is_calistir, args=(is_id, istek), daemon=True)
    th.start()
    return CloneCevap(basarili=True, is_id=is_id)


@app.get("/clone/job/{is_id}")
def is_durumu(is_id: str) -> Dict[str, Any]:
    with _kilit:
        durum = _isler.get(is_id)
    if durum is None:
        raise HTTPException(status_code=404, detail="İş bulunamadı")
    return durum.model_dump()


@app.post("/upload")
async def dosya_yukle(dosya: UploadFile = File(...)) -> Dict[str, Any]:
    """Ses dosyası yükle, kalıcı yol döndür."""
    if not dosya.filename:
        raise HTTPException(status_code=400, detail="Dosya adı yok")
    ts = int(time.time() * 1000)
    guvenli_isim = f"{ts}_{Path(dosya.filename).name}"
    hedef = YUKLEME_KOK / guvenli_isim
    with hedef.open("wb") as f:
        shutil.copyfileobj(dosya.file, f)
    return {"basarili": True, "yol": str(hedef), "isim": guvenli_isim}


@app.get("/cikti/{is_id}")
def cikti_indir(is_id: str):
    with _kilit:
        durum = _isler.get(is_id)
    if durum is None or not durum.cikti_yolu:
        raise HTTPException(status_code=404, detail="Çıktı yok")
    return FileResponse(durum.cikti_yolu, media_type="audio/wav")


# ===================== MODEL IMPORT (.pth + .index) =====================
_UPLOAD_CHUNK = 4 * 1024 * 1024  # 4 MB


def _guvenli_isim(ad: str) -> str:
    """Path traversal koruma — sadece dosya adi."""
    return Path(ad or "model").name.replace("..", "_")


async def _async_yaz(dosya: UploadFile, hedef: Path) -> int:
    toplam = 0
    with hedef.open("wb") as f:
        while True:
            chunk = await dosya.read(_UPLOAD_CHUNK)
            if not chunk:
                break
            f.write(chunk)
            toplam += len(chunk)
    return toplam


@app.post("/modeller/import")
async def rvc_model_import(
    pth_dosya: UploadFile = File(...),
    index_dosya: Optional[UploadFile] = File(None),
    isim: str = Form(...),
    karakter_id: Optional[str] = Form(None),
):
    """RVC ses modeli (.pth + opsiyonel .index) import.
    cikti_kok/modeller/{isim}.pth (+ .index) olarak kaydedilir."""
    try:
        isim_guv = _guvenli_isim(isim).replace(".pth", "").replace(".index", "")
        if not isim_guv:
            return {"basarili": False, "hata": "Gecersiz isim"}
        pth_ad = _guvenli_isim(pth_dosya.filename or "model.pth")
        if not pth_ad.lower().endswith(".pth"):
            return {"basarili": False,
                    "hata": "pth_dosya .pth uzantili olmali"}

        pth_hedef = MODELLER_KOK / f"{isim_guv}.pth"
        if pth_hedef.exists():
            return {"basarili": False,
                    "hata": f"Bu isimde model var: {isim_guv}"}
        pth_boyut = await _async_yaz(pth_dosya, pth_hedef)

        index_yolu = None
        index_boyut = 0
        if index_dosya is not None and index_dosya.filename:
            ix_ad = _guvenli_isim(index_dosya.filename)
            if not ix_ad.lower().endswith(".index"):
                return {"basarili": False,
                        "hata": "index_dosya .index uzantili olmali"}
            ix_hedef = MODELLER_KOK / f"{isim_guv}.index"
            index_boyut = await _async_yaz(index_dosya, ix_hedef)
            index_yolu = str(ix_hedef)

        meta = {
            "isim": isim_guv,
            "karakter_id": karakter_id,
            "kaynak": "import",
            "pth_yolu": str(pth_hedef),
            "index_yolu": index_yolu,
            "pth_boyut_byte": pth_boyut,
            "index_boyut_byte": index_boyut,
            "import_tarih": time.time(),
        }
        (MODELLER_KOK / f"{isim_guv}.json").write_text(
            json.dumps(_json_temizle(meta), ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        return {"basarili": True, "isim": isim_guv,
                "pth_yolu": str(pth_hedef),
                "index_yolu": index_yolu, "meta": meta}
    except Exception as e:
        traceback.print_exc()
        return {"basarili": False, "hata": f"{type(e).__name__}: {e}"}


@app.post("/modeller/import_url")
async def rvc_model_import_url(
    pth_url: str = Form(...),
    isim: str = Form(...),
    index_url: Optional[str] = Form(None),
    karakter_id: Optional[str] = Form(None),
):
    """HuggingFace direct URL'den .pth (+ opsiyonel .index) indir."""
    try:
        isim_guv = _guvenli_isim(isim).replace(".pth", "").replace(".index", "")
        if not isim_guv:
            return {"basarili": False, "hata": "Gecersiz isim"}
        if not (pth_url.startswith("http://") or pth_url.startswith("https://")):
            return {"basarili": False, "hata": "Gecersiz pth_url"}
        from urllib.request import urlopen, Request

        pth_hedef = MODELLER_KOK / f"{isim_guv}.pth"
        if pth_hedef.exists():
            return {"basarili": False,
                    "hata": f"Bu isimde model var: {isim_guv}"}

        def _indir(url: str, hedef: Path) -> int:
            req = Request(url, headers={"User-Agent": "FikoYT-Importer/1.0"})
            with urlopen(req, timeout=600) as r, hedef.open("wb") as f:
                t = 0
                while True:
                    chunk = r.read(_UPLOAD_CHUNK)
                    if not chunk:
                        break
                    f.write(chunk)
                    t += len(chunk)
                return t

        loop = asyncio.get_event_loop()
        pth_boyut = await loop.run_in_executor(None, _indir, pth_url, pth_hedef)

        index_yolu = None
        index_boyut = 0
        if index_url and (index_url.startswith("http://")
                          or index_url.startswith("https://")):
            ix_hedef = MODELLER_KOK / f"{isim_guv}.index"
            index_boyut = await loop.run_in_executor(
                None, _indir, index_url, ix_hedef,
            )
            index_yolu = str(ix_hedef)

        meta = {
            "isim": isim_guv,
            "karakter_id": karakter_id,
            "kaynak": "url",
            "pth_url": pth_url,
            "index_url": index_url,
            "pth_yolu": str(pth_hedef),
            "index_yolu": index_yolu,
            "pth_boyut_byte": pth_boyut,
            "index_boyut_byte": index_boyut,
            "import_tarih": time.time(),
        }
        (MODELLER_KOK / f"{isim_guv}.json").write_text(
            json.dumps(_json_temizle(meta), ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        return {"basarili": True, "isim": isim_guv,
                "pth_yolu": str(pth_hedef),
                "index_yolu": index_yolu, "meta": meta}
    except Exception as e:
        traceback.print_exc()
        return {"basarili": False, "hata": f"{type(e).__name__}: {e}"}


@app.get("/modeller/list")
def modeller_listele() -> Dict[str, Any]:
    """Sistemdeki RVC modellerini listele:
    - import edilenler (cikti_kok/modeller/)
    - RVC kurulumundakiler (assets/weights vs.)"""
    try:
        import_modeller: List[Dict[str, Any]] = []
        for f in sorted(MODELLER_KOK.glob("*.pth")):
            meta_y = f.with_suffix(".json")
            meta = {}
            if meta_y.exists():
                try:
                    meta = json.loads(meta_y.read_text(encoding="utf-8"))
                except Exception:
                    pass
            ix = f.with_suffix(".index")
            import_modeller.append({
                "isim": f.stem,
                "pth_yolu": str(f),
                "index_yolu": str(ix) if ix.exists() else None,
                "boyut_mb": round(f.stat().st_size / 1e6, 1),
                "kaynak": "import",
                "meta": meta,
            })

        rvc_modeller: List[Dict[str, Any]] = []
        if RVC_PATH is not None:
            olasi_dizinler = [
                RVC_PATH / "assets" / "weights",
                RVC_PATH / "weights",
                RVC_PATH / "models",
            ]
            for d in olasi_dizinler:
                if not d.exists():
                    continue
                for pth in d.rglob("*.pth"):
                    rvc_modeller.append({
                        "isim": pth.stem,
                        "pth_yolu": str(pth),
                        "boyut_mb": round(pth.stat().st_size / 1e6, 1),
                        "kaynak": "rvc",
                    })
        return _json_temizle({
            "basarili": True,
            "import_modeller": import_modeller,
            "rvc_modeller": rvc_modeller,
        })
    except Exception as e:
        return {"basarili": False, "hata": f"{type(e).__name__}: {e}"}


@app.delete("/modeller/{isim}")
def model_sil(isim: str):
    """Import edilen RVC modelini sil (.pth + .index + .json)."""
    try:
        isim_guv = _guvenli_isim(isim).replace(".pth", "").replace(".index", "")
        if not isim_guv:
            return {"basarili": False, "hata": "Gecersiz isim"}
        silindi = False
        for ext in (".pth", ".index", ".json"):
            x = MODELLER_KOK / f"{isim_guv}{ext}"
            if x.exists():
                try:
                    x.unlink()
                    silindi = True
                except Exception as e:
                    print(f"[Sunucu] Silme hatasi: {e}")
        return {"basarili": silindi, "isim": isim_guv}
    except Exception as e:
        return {"basarili": False, "hata": f"{type(e).__name__}: {e}"}


# ===================== Çalıştırıcı =====================
def main():
    parser = argparse.ArgumentParser(description="FikoYT RVC Voice Clone API")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8004)
    args = parser.parse_args()

    print("=" * 60)
    print(" FikoYT RVC Voice Clone API")
    print(f"  http://{args.host}:{args.port}/docs")
    print(f"  RVC kurulu mu: {RVC_PATH is not None}")
    if RVC_PATH:
        print(f"  RVC yol: {RVC_PATH}")
    else:
        print("  UYARI: RVC kurulu değil — endpoint'ler stub döner.")
    print("=" * 60)

    import uvicorn
    uvicorn.run(app, host=args.host, port=args.port)


if __name__ == "__main__":
    main()
