"""ACE-Step LoRA Eğitim Subprocess Wrapper
==========================================
ACE-Step'in `train.py` CLI'ını alt süreçte çalıştırır.
- stdout satır satır parse → epoch/step/loss yakalanır
- iptal_kontrol döndürürse process.terminate
- Çıktı .safetensors hedef yola taşınır
- Yan dosya meta.json yazılır
"""
from __future__ import annotations

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 json
import os
import re
import shutil
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Callable, Dict, Any, Optional

# ACE-Step kurulum yolu
ACE_STEP_KOK = Path(
    "D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/video-uretim/ace-step/ACE-Step-1.5"
).resolve()
ACE_STEP_VENV_PY = ACE_STEP_KOK / ".venv" / "Scripts" / "python.exe"
ACE_STEP_TRAIN = ACE_STEP_KOK / "train.py"
ACE_STEP_CHECKPOINTS = ACE_STEP_KOK / "checkpoints"

# Regex desenleri — train.py logları için
_RX_EPOCH = re.compile(r"epoch\s+(\d+)\s*/\s*(\d+)", re.IGNORECASE)
_RX_STEP = re.compile(r"step\s+(\d+)\s*/\s*(\d+)", re.IGNORECASE)
_RX_LOSS = re.compile(r"loss[=:]\s*([0-9]+\.[0-9]+)", re.IGNORECASE)
_RX_GLOBAL = re.compile(r"global[_ ]step[=:]\s*(\d+)", re.IGNORECASE)


def _python_yolu() -> str:
    """ACE-Step venv yoksa system python'a düş."""
    if ACE_STEP_VENV_PY.exists():
        return str(ACE_STEP_VENV_PY)
    return sys.executable or "python"


def _final_safetensors_bul(output_dir: Path) -> Optional[Path]:
    """Eğitim sonrası output_dir altından .safetensors dosyası bul.
    En son üretilen / en büyük olanı tercih et."""
    if not output_dir.exists():
        return None
    adaylar = list(output_dir.rglob("*.safetensors"))
    if not adaylar:
        return None
    # 'final' içeren öncelikli, yoksa en yeni
    final_olanlar = [p for p in adaylar if "final" in p.name.lower()]
    if final_olanlar:
        return max(final_olanlar, key=lambda p: p.stat().st_mtime)
    return max(adaylar, key=lambda p: p.stat().st_mtime)


def ace_lora_egit(
    sanatci_id: str,
    lora_isim: str,
    dataset_klasoru: str,
    cikti_yolu: str,
    base_model_variant: str = "turbo",
    train_mode: str = "fixed",
    epoch: int = 500,
    batch_size: int = 1,
    lr: float = 1e-4,
    karakter_id: Optional[str] = None,
    voice_clone_etkin: bool = False,
    ilerleme_cb: Optional[Callable[[int, int, str, float], None]] = None,
    iptal_kontrol: Optional[Callable[[], bool]] = None,
) -> Dict[str, Any]:
    """ACE-Step LoRA eğitimini subprocess ile başlatır.

    cikti_yolu: hedef .safetensors yolu (örn. .../loralar/<job_id>.safetensors)
    train_mode: 'fixed' | 'vanilla'
    ilerleme_cb(adim, toplam, faz, son_loss)
    iptal_kontrol() True dönerse süreç sonlandırılır.

    Dönüş: { basarili, cikti_yolu, son_loss, sure, hata?, meta }
    """
    t0 = time.time()
    son_loss: float = 0.0
    son_epoch: int = 0
    son_step: int = 0
    toplam_step: int = max(1, epoch)
    son_faz: str = "Hazırlanıyor"

    if not ACE_STEP_TRAIN.exists():
        return {
            "basarili": False,
            "hata": f"ACE-Step train.py bulunamadı: {ACE_STEP_TRAIN}",
        }

    dataset_path = Path(dataset_klasoru).resolve()
    if not dataset_path.exists():
        return {
            "basarili": False,
            "hata": f"Dataset klasörü yok: {dataset_path}",
        }

    cikti_yolu_p = Path(cikti_yolu).resolve()
    cikti_yolu_p.parent.mkdir(parents=True, exist_ok=True)
    # Geçici output_dir — train.py kendi alt klasörlerini oluşturur
    gecici_output = cikti_yolu_p.parent / f"_gecici_{cikti_yolu_p.stem}"
    gecici_output.mkdir(parents=True, exist_ok=True)

    if train_mode not in ("fixed", "vanilla"):
        train_mode = "fixed"

    cmd = [
        _python_yolu(),
        str(ACE_STEP_TRAIN),
        train_mode,
        "--checkpoint-dir", str(ACE_STEP_CHECKPOINTS),
        "--model-variant", base_model_variant,
        "--dataset-dir", str(dataset_path),
        "--output-dir", str(gecici_output),
        "--epochs", str(epoch),
        "--batch-size", str(batch_size),
        "--learning-rate", str(lr),
    ]

    if ilerleme_cb:
        try:
            ilerleme_cb(0, toplam_step, "Subprocess başlatılıyor", 0.0)
        except Exception:
            pass

    env = os.environ.copy()
    env["PYTHONIOENCODING"] = "utf-8"
    env["PYTHONUNBUFFERED"] = "1"

    proc = subprocess.Popen(
        cmd,
        cwd=str(ACE_STEP_KOK),
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        env=env,
        text=True,
        encoding="utf-8",
        errors="replace",
        bufsize=1,
    )

    iptal_edildi = False
    try:
        assert proc.stdout is not None
        for line in proc.stdout:
            print(f"[train] {line.rstrip()}")

            # Parse satır
            m_epoch = _RX_EPOCH.search(line)
            if m_epoch:
                son_epoch = int(m_epoch.group(1))
                toplam_step = max(toplam_step, int(m_epoch.group(2)))
                son_faz = f"Epoch {son_epoch}/{toplam_step}"
            m_step = _RX_STEP.search(line)
            if m_step:
                son_step = int(m_step.group(1))
            m_loss = _RX_LOSS.search(line)
            if m_loss:
                try:
                    son_loss = float(m_loss.group(1))
                except Exception:
                    pass
            m_global = _RX_GLOBAL.search(line)
            if m_global:
                son_step = max(son_step, int(m_global.group(1)))

            # İlerleme callback
            if ilerleme_cb:
                try:
                    ilerleme_cb(son_epoch, toplam_step, son_faz, son_loss)
                except Exception:
                    pass

            # İptal kontrolü
            if iptal_kontrol and iptal_kontrol():
                iptal_edildi = True
                try:
                    proc.terminate()
                except Exception:
                    pass
                break

        proc.wait(timeout=30 if iptal_edildi else None)
    except subprocess.TimeoutExpired:
        try:
            proc.kill()
        except Exception:
            pass
    except Exception as e:
        try:
            proc.terminate()
        except Exception:
            pass
        return {
            "basarili": False,
            "hata": f"Subprocess hata: {type(e).__name__}: {e}",
            "sure": time.time() - t0,
        }

    if iptal_edildi:
        return {
            "basarili": False,
            "hata": "İptal edildi",
            "sure": time.time() - t0,
        }

    if proc.returncode != 0:
        return {
            "basarili": False,
            "hata": f"Eğitim başarısız (exit={proc.returncode})",
            "son_loss": son_loss,
            "sure": time.time() - t0,
        }

    # Çıktıyı bul ve hedef yola taşı
    final_path = _final_safetensors_bul(gecici_output)
    if final_path is None:
        return {
            "basarili": False,
            "hata": f"Çıktı .safetensors bulunamadı: {gecici_output}",
            "sure": time.time() - t0,
        }

    try:
        if cikti_yolu_p.exists():
            cikti_yolu_p.unlink()
        shutil.move(str(final_path), str(cikti_yolu_p))
    except Exception as e:
        return {
            "basarili": False,
            "hata": f"Dosya taşıma hatası: {e}",
            "sure": time.time() - t0,
        }

    sure = time.time() - t0
    meta = {
        "sanatci_id": sanatci_id,
        "lora_isim": lora_isim,
        "base_variant": base_model_variant,
        "train_mode": train_mode,
        "epoch": epoch,
        "batch_size": batch_size,
        "lr": lr,
        "son_loss": son_loss,
        "son_epoch": son_epoch,
        "sure": sure,
        "tarih": datetime.now().isoformat(timespec="seconds"),
        "karakter_id": karakter_id,
        "dataset_klasoru": str(dataset_path),
    }

    # Meta JSON yaz
    try:
        meta_yolu = cikti_yolu_p.with_suffix(".json")
        meta_yolu.write_text(
            json.dumps(meta, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
    except Exception as e:
        print(f"[ace_lora_egit] Meta yazılamadı: {e}")

    # Geçici output klasörünü temizle
    try:
        shutil.rmtree(gecici_output, ignore_errors=True)
    except Exception:
        pass

    # Voice clone (opsiyonel — başarısız olsa eğitim yine başarılı sayılır)
    if voice_clone_etkin and karakter_id:
        try:
            _voice_clone_embedding_uret(
                dataset_path, karakter_id, cikti_yolu_p.parent
            )
            meta["voice_clone"] = True
        except Exception as e:
            print(f"[ace_lora_egit] Voice clone atlandı: {e}")
            meta["voice_clone"] = False

    return {
        "basarili": True,
        "cikti_yolu": str(cikti_yolu_p),
        "son_loss": son_loss,
        "sure": sure,
        "meta": meta,
    }


def _voice_clone_embedding_uret(
    dataset_path: Path, karakter_id: str, cikti_klasoru: Path
) -> None:
    """RVC venv yoksa atla. Bu basit stub — gerçek embedding üretimi için
    RVC veya benzeri bir wrapper'a bağlanır."""
    rvc_kok = Path(
        "D:/__sistem__/Masaustu/FikoYT.Yeni.Cag/video-uretim/ace-step/RVC"
    )
    if not rvc_kok.exists():
        raise RuntimeError("RVC kurulumu yok — voice clone atlandı")
    # NOT: gerçek RVC entegrasyonu için ayrı subprocess çağrısı gerekir.
    # Şu anlık placeholder.
    print(f"[voice_clone] {karakter_id} için RVC embedding stub")


if __name__ == "__main__":
    # Basit CLI test
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("--sanatci", required=True)
    p.add_argument("--isim", required=True)
    p.add_argument("--dataset", required=True)
    p.add_argument("--cikti", required=True)
    p.add_argument("--epoch", type=int, default=500)
    p.add_argument("--batch", type=int, default=1)
    p.add_argument("--lr", type=float, default=1e-4)
    p.add_argument("--variant", default="turbo")
    p.add_argument("--mode", default="fixed")
    args = p.parse_args()
    sonuc = ace_lora_egit(
        sanatci_id=args.sanatci,
        lora_isim=args.isim,
        dataset_klasoru=args.dataset,
        cikti_yolu=args.cikti,
        base_model_variant=args.variant,
        train_mode=args.mode,
        epoch=args.epoch,
        batch_size=args.batch,
        lr=args.lr,
    )
    print(json.dumps(sonuc, ensure_ascii=False, indent=2))
