"""
Character domain — karakter bank, loader, refs batch + LoRA export pipeline +
identity preservation helpers (PuLID / InstantID / IPAdapter v2).

Node sınıfları:
  - FikoYT_MVD_CharacterBank
  - FikoYT_MVD_CharacterLoader
  - FikoYT_MVD_CharacterRefsBatch

Identity helpers (Sprint 1 — W12):
  - _try_apply_instantid(...)
  - _try_apply_ipadapter_v2(...)
  - _apply_identity(method, ...) — dispatcher

Helper imza sözleşmesi:
  (model, ref_tensor, weight, **opts) -> (patched_model, status_str)

Helper kurulu değilse model'i değiştirmeden döner; status_str hata/atlama bilgisini
taşır. _try_apply_pulid bu sözleşmenin kanonik referansıdır
(bkz. _legacy.py:5525).
"""
from ._legacy import (
    FikoYT_MVD_CharacterBank,
    FikoYT_MVD_CharacterLoader,
    FikoYT_MVD_CharacterRefsBatch,
    _chars_dir,
    _char_path,
    _char_descriptor,
    _char_pronouns,
    _save_character,
    _load_character,
    _list_characters,
    _delete_character,
    _char_refs_dir,
    _list_character_refs,
    _add_character_ref,
    _delete_character_ref,
    _set_character_caption,
    _write_kohya_toml,
    _write_train_script,
    _lora_export_character,
    _load_char_image_tensor,
    _try_apply_pulid,
)


def _try_apply_pulid_v2(model, character_image, weight=0.85, start_at=0.0, end_at=0.85,
                         pulid_file="pulid_flux_v0.9.2.safetensors"):
    """Sprint 20 (W22): PuLID Flux II (v0.9.2+) — yeni nesil model pollution çözümü.

    Mevcut `_try_apply_pulid` (v0.9.1) ile PARALEL. Aynı node sınıflarını kullanır
    (`PulidFluxModelLoader`, `ApplyPulidFlux`), sadece model dosyası farklı.

    PuLID v2 farkları:
      - Model pollution azaldı → "auto" identity mode'da daha sağlam
      - TeaCache/WaveSpeed uyumlu (Sprint 14)
      - end_at=0.85 default (v1'de 0.8'di) → kimlik daha geç bırakılır

    Dosya: models/pulid/pulid_flux_v0.9.2.safetensors (HF: guozinan/PuLID)
    """
    if character_image is None:
        return model, "PuLID v2: karakter referansı yok"
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM
        cls_loader = _NCM.get("PulidFluxModelLoader")
        cls_eva = _NCM.get("PulidFluxEvaClipLoader")
        cls_if = _NCM.get("PulidFluxInsightFaceLoader")
        cls_apply = _NCM.get("ApplyPulidFlux")
        if not all([cls_loader, cls_eva, cls_if, cls_apply]):
            return model, ("PuLID v2 node'ları yok. "
                           "Install: ComfyUI_PuLID_Flux_ll (v0.9.2+ destekli)")

        # v2 modelini yükle (v1 yerine)
        try:
            pulid_flux = cls_loader().load_model(pulid_file)[0]
        except Exception as e:
            # v2 yok, v1'e fallback
            print(f"[FikoYT] PuLID v2 dosya yok, v1'e düşülüyor: {e}")
            return _try_apply_pulid(model, character_image, weight=weight)

        eva_clip = cls_eva().load_eva_clip()[0]
        face_an = cls_if().load_insightface("CUDA")[0]
        n_refs = (character_image.shape[0]
                  if hasattr(character_image, 'shape') and len(character_image.shape) >= 4
                  else 1)
        try:
            patched = cls_apply().apply_pulid_flux(
                model=model, pulid_flux=pulid_flux, eva_clip=eva_clip, face_analysis=face_an,
                image=character_image, weight=weight,
                start_at=start_at, end_at=end_at,  # v2: 0.85
                fusion="mean", fusion_weight_max=1.0, fusion_weight_min=0.0,
                train_step=1000, use_gray=True, attn_mask=None
            )[0]
            return patched, (f"✓ PuLID v2 uygulandı (model={pulid_file}, "
                              f"{n_refs} ref, w={weight}, end@{end_at})")
        except Exception as batch_err:
            if n_refs > 1:
                print(f"[FikoYT] PuLID v2 batched fail ({batch_err}), sequential")
                patched = model
                per_weight = weight / n_refs
                for idx in range(n_refs):
                    single = character_image[idx:idx + 1]
                    patched = cls_apply().apply_pulid_flux(
                        model=patched, pulid_flux=pulid_flux, eva_clip=eva_clip,
                        face_analysis=face_an, image=single, weight=per_weight,
                        start_at=start_at, end_at=end_at,
                        fusion="mean", fusion_weight_max=1.0, fusion_weight_min=0.0,
                        train_step=1000, use_gray=True, attn_mask=None
                    )[0]
                return patched, f"✓ PuLID v2 (seq, {n_refs} refs, w={weight})"
            raise
    except Exception as e:
        import traceback
        print(traceback.format_exc()[-500:])
        return model, f"PuLID v2 hata: {e}"


def _try_apply_instantid(model, positive_cond, negative_cond, face_image,
                         weight=0.8, start_at=0.0, end_at=1.0):
    """ComfyUI_InstantID (cubiq) yüklü ise InstantID uygula.

    InstantID: face landmark + face embedding tabanlı kimlik koruma.
    SDXL için resmi destek; Flux'ta sınırlı (status mesajında belirtilir).

    Dönüş: (patched_model, patched_positive, patched_negative, status_str).
    Kurulu değilse model + cond'lar değiştirilmeden döner.
    """
    if face_image is None:
        return model, positive_cond, negative_cond, "InstantID: yüz referansı yok"
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM
        cls_loader = _NCM.get("InstantIDModelLoader")
        cls_face = _NCM.get("InstantIDFaceAnalysis")
        cls_cn = _NCM.get("ControlNetLoader")
        cls_apply = _NCM.get("ApplyInstantID") or _NCM.get("ApplyInstantIDAdvanced")

        if not all([cls_loader, cls_face, cls_apply]):
            return model, positive_cond, negative_cond, (
                "InstantID node'ları yok. Install: ComfyUI Manager > "
                "'ComfyUI_InstantID' (cubiq). Modeller: models/instantid/ip-adapter.bin + "
                "models/controlnet/instantid (InstantX/InstantID HF)")

        # Modelleri yükle
        try:
            ipadapter_model = cls_loader().load_model("ip-adapter.bin")[0]
        except Exception as e:
            return model, positive_cond, negative_cond, f"InstantID model yüklenemedi: {e}"

        try:
            face_analysis = cls_face().load_insight_face("CUDA")[0]
        except Exception:
            try:
                face_analysis = cls_face().load_insight_face("CPU")[0]
            except Exception as e:
                return model, positive_cond, negative_cond, f"InsightFace yüklenemedi: {e}"

        # ControlNet model (InstantID için diffusion_pytorch_model.safetensors)
        controlnet = None
        if cls_cn:
            for cn_name in ("instantid/diffusion_pytorch_model.safetensors",
                            "InstantID/diffusion_pytorch_model.safetensors",
                            "diffusion_pytorch_model.safetensors"):
                try:
                    controlnet = cls_cn().load_controlnet(cn_name)[0]
                    break
                except Exception:
                    continue

        try:
            result = cls_apply().apply_instantid(
                instantid=ipadapter_model,
                insightface=face_analysis,
                control_net=controlnet,
                image=face_image,
                model=model,
                positive=positive_cond,
                negative=negative_cond,
                ip_weight=float(weight),
                cn_strength=float(weight) * 0.8,
                start_at=float(start_at),
                end_at=float(end_at),
            )
            # Apply node 3 değer döner: (model, positive, negative)
            if isinstance(result, (tuple, list)) and len(result) >= 3:
                return result[0], result[1], result[2], (
                    f"✓ InstantID uygulandı (w={weight}, end@{end_at})")
            return model, positive_cond, negative_cond, "InstantID apply: beklenmeyen dönüş"
        except Exception as e:
            return model, positive_cond, negative_cond, f"InstantID apply hata: {e}"
    except Exception as e:
        import traceback
        print(traceback.format_exc()[-500:])
        return model, positive_cond, negative_cond, f"InstantID hata: {e}"


def _try_apply_ipadapter_v2(model, ref_tensor, weight=0.7, style="full-face",
                            start_at=0.0, end_at=1.0):
    """IPAdapter v2 (FaceID-Plus-v2) — h94/IP-Adapter modelleri.

    Karakter yüz/stil tutarlılığı için cubiq IPAdapter_plus üzerinden FaceID-Plus-v2
    preset'i kullanır. SDXL hedefli; Flux modellerinde ICAdapter-Flux pipeline'ı
    feature-detect edilir.

    style: "full-face" | "plus-face" | "style-transfer" | "composition"

    Dönüş: (patched_model, status_str). Kurulu değilse model değişmez.
    """
    if ref_tensor is None or weight <= 0:
        return model, "IPAdapter v2: ref yok veya weight=0"
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM

        # cubiq ComfyUI_IPAdapter_plus
        unified = _NCM.get("IPAdapterUnifiedLoaderFaceID") or _NCM.get("IPAdapterUnifiedLoader")
        face_advanced = _NCM.get("IPAdapterFaceID") or _NCM.get("IPAdapterAdvanced")

        if not (unified and face_advanced):
            return model, ("IPAdapter v2 node'ları yok. Install: ComfyUI Manager > "
                           "'ComfyUI_IPAdapter_plus' (cubiq). Model: "
                           "models/ipadapter/ip-adapter-faceid-plusv2_sdxl.bin + "
                           "models/loras/ip-adapter-faceid-plusv2_sdxl_lora.safetensors")

        preset_map = {
            "full-face":      "FACEID PLUS V2",
            "plus-face":      "FACEID PORTRAIT (SDXL)",
            "style-transfer": "STANDARD (medium strength)",
            "composition":    "FACEID PLUS V2",
        }
        preset = preset_map.get(style, "FACEID PLUS V2")
        weight_type_map = {
            "full-face":      "linear",
            "plus-face":      "linear",
            "style-transfer": "style transfer",
            "composition":    "composition",
        }
        wt = weight_type_map.get(style, "linear")

        try:
            loaded = unified().load_models(model, preset)
            if isinstance(loaded, (tuple, list)) and len(loaded) >= 2:
                model_loaded, ip_model = loaded[0], loaded[1]
            else:
                model_loaded, ip_model = model, loaded
        except Exception as e:
            return model, f"IPAdapter v2 unified loader hata: {e}"

        try:
            # IPAdapterFaceID özel imza (insightface ile face embed); fallback Advanced
            if face_advanced.__name__ == "IPAdapterFaceID" or hasattr(face_advanced, "apply_ipadapter_faceid"):
                method = getattr(face_advanced(), "apply_ipadapter_faceid", None) or \
                         getattr(face_advanced(), "apply_ipadapter", None)
            else:
                method = getattr(face_advanced(), "apply_ipadapter", None)
            if method is None:
                return model, "IPAdapter v2: apply metodu yok"
            patched = method(
                model_loaded, ip_model, ref_tensor,
                weight=float(weight), weight_type=wt,
                start_at=float(start_at), end_at=float(end_at),
                embeds_scaling="V only",
            )[0]
            return patched, f"✓ IPAdapter v2 uygulandı (style={style}, w={weight})"
        except Exception as e:
            return model, f"IPAdapter v2 apply hata: {e}"
    except Exception as e:
        import traceback
        print(traceback.format_exc()[-500:])
        return model, f"IPAdapter v2 hata: {e}"


def _try_apply_reactor(target_image, source_face_image, enabled=True,
                       face_index=0, face_restore="GFPGANv1.4.pth", restore_visibility=0.5):
    """Sprint 4 (W15): ReActor face swap.

    target_image: Üretilmiş veya post-process edilecek IMAGE tensor (BHWC)
    source_face_image: Kaynak yüz tensor'u

    ReActor lisansı gri alan — UI'da opt-in toggle olmalı.
    Kurulu değilse target_image'ı değiştirmeden döner (graceful fallback).
    """
    if not enabled or source_face_image is None:
        return target_image, "ReActor: kapalı veya kaynak yok"
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM
        cls = _NCM.get("ReActorFaceSwap") or _NCM.get("ReActorFaceSwapOpt")
        if not cls:
            return target_image, ("ReActor node'u yok. Install: "
                                  "ComfyUI Manager > 'ComfyUI-ReActor-Node'")
        try:
            node = cls()
            method = getattr(node, "execute", None) or getattr(node, "swap", None)
            if not method:
                return target_image, "ReActor: execute metodu yok"
            try:
                result = method(
                    enabled=True, input_image=target_image, source_image=source_face_image,
                    face_model="inswapper_128.onnx",
                    swap_model="inswapper_128.onnx",
                    facedetection="retinaface_resnet50",
                    face_restore_model=face_restore,
                    face_restore_visibility=restore_visibility,
                    codeformer_weight=0.5,
                    detect_gender_input="no",
                    detect_gender_source="no",
                    input_faces_index=str(face_index),
                    source_faces_index="0",
                    console_log_level=1,
                )
            except TypeError:
                # Eski API
                result = method(target_image, source_face_image, face_index)
            out = result[0] if isinstance(result, (tuple, list)) else result
            return out, f"✓ ReActor face swap uygulandı (index={face_index})"
        except Exception as e:
            return target_image, f"ReActor apply hata: {e}"
    except Exception as e:
        import traceback
        print(traceback.format_exc()[-500:])
        return target_image, f"ReActor hata: {e}"


def _apply_identity(method, model, ref_tensor, positive_cond=None, negative_cond=None,
                    weight=0.85, **opts):
    """Identity yöntemi dispatcher — Sprint 1 W12.

    method: "pulid" | "instantid" | "ipadapter_v2" | "auto" | "none"
      - "auto": InstantID → PuLID → IPAdapter v2 → text-only (kurulu olana göre)
      - "none": hiçbiri uygulanmaz (text-only render)

    Tüm yöntemler kurulu değilse model + cond'lar değiştirilmeden döner;
    kullanıcı text-only fallback alır.

    Dönüş: dict {
        "model": patched_model,
        "positive": cond (sadece InstantID değiştirir),
        "negative": cond,
        "method_used": "pulid" | "instantid" | "ipadapter_v2" | "none",
        "status": str  # log/UI mesajı,
    }
    """
    if not method:
        method = "auto"
    method = method.lower().strip()

    if method == "none" or ref_tensor is None:
        return {
            "model": model,
            "positive": positive_cond,
            "negative": negative_cond,
            "method_used": "none",
            "status": "Identity uygulanmadı (method=none veya ref=None)",
        }

    # Sıralı dene: "auto" durumunda kurulu olan ilk yöntem kazanır
    if method == "auto":
        # auto: PuLID v2 (Flux II) > PuLID v1 > InstantID > IPAdapter v2
        # v2 model varsa öncelikli (model pollution çözüldü, daha sağlam)
        order = ["pulid_v2", "pulid", "instantid", "ipadapter_v2"]
    else:
        order = [method]

    statuses = []
    for m in order:
        if m == "pulid_v2":
            # Sprint 20: PuLID Flux II
            pulid_file = opts.get("pulid_v2_file", "pulid_flux_v0.9.2.safetensors")
            patched, status = _try_apply_pulid_v2(model, ref_tensor, weight=weight,
                                                    pulid_file=pulid_file)
            statuses.append(status)
            if status.startswith("✓"):
                return {
                    "model": patched, "positive": positive_cond,
                    "negative": negative_cond,
                    "method_used": "pulid_v2",
                    "status": " | ".join(statuses),
                }
        elif m == "pulid":
            patched, status = _try_apply_pulid(model, ref_tensor, weight=weight)
            statuses.append(status)
            # "✓" işareti varsa başarılı sayılır
            if status.startswith("✓"):
                return {
                    "model": patched,
                    "positive": positive_cond,
                    "negative": negative_cond,
                    "method_used": "pulid",
                    "status": " | ".join(statuses),
                }
        elif m == "instantid":
            patched_model, patched_pos, patched_neg, status = _try_apply_instantid(
                model, positive_cond, negative_cond, ref_tensor, weight=weight)
            statuses.append(status)
            if status.startswith("✓"):
                return {
                    "model": patched_model,
                    "positive": patched_pos,
                    "negative": patched_neg,
                    "method_used": "instantid",
                    "status": " | ".join(statuses),
                }
        elif m == "ipadapter_v2":
            style = opts.get("style", "full-face")
            patched, status = _try_apply_ipadapter_v2(
                model, ref_tensor, weight=weight, style=style)
            statuses.append(status)
            if status.startswith("✓"):
                return {
                    "model": patched,
                    "positive": positive_cond,
                    "negative": negative_cond,
                    "method_used": "ipadapter_v2",
                    "status": " | ".join(statuses),
                }
        else:
            statuses.append(f"Bilinmeyen identity method: {m}")

    # Hiçbiri başarılı olmadı → text-only fallback
    return {
        "model": model,
        "positive": positive_cond,
        "negative": negative_cond,
        "method_used": "none",
        "status": " | ".join(statuses) or "Identity yöntemi bulunamadı",
    }


__all__ = [
    "FikoYT_MVD_CharacterBank",
    "FikoYT_MVD_CharacterLoader",
    "FikoYT_MVD_CharacterRefsBatch",
    "_chars_dir",
    "_char_path",
    "_char_descriptor",
    "_char_pronouns",
    "_save_character",
    "_load_character",
    "_list_characters",
    "_delete_character",
    "_char_refs_dir",
    "_list_character_refs",
    "_add_character_ref",
    "_delete_character_ref",
    "_set_character_caption",
    "_write_kohya_toml",
    "_write_train_script",
    "_lora_export_character",
    "_load_char_image_tensor",
    "_try_apply_pulid",
    "_try_apply_pulid_v2",
    "_try_apply_instantid",
    "_try_apply_ipadapter_v2",
    "_apply_identity",
    "_try_apply_reactor",
]
