"""요소 변형 관계 추출 — 이름 기반 후보 + LLM 확인."""
import logging
from typing import Dict, List, Optional

from app.modules.llm.llm_client import call_structured
from app.modules.prompt_loader import load_prompt, load_schema
from app.modules.name_matcher import base_name

logger = logging.getLogger(__name__)
_MODULE = "entity_relation"

_ETYPE_SINGULAR = {"characters": "character", "locations": "location", "props": "prop"}


def _detect_candidates(entities: Dict[str, List[Dict]]) -> List[Dict]:
    """이름 기반으로 같은 타입 내 변형 후보 쌍을 추출."""
    candidates = []

    for etype in ["characters", "locations", "props"]:
        singular = _ETYPE_SINGULAR[etype]
        items = entities.get(etype, [])

        # base_name → [(short_id, name)] 그룹핑
        groups: Dict[str, list] = {}
        for e in items:
            sid = e.get("short_id", "")
            name = e.get("name", "")
            if not sid or not name:
                continue
            bn = base_name(name)
            if bn:
                groups.setdefault(bn, []).append((sid, name))

        # 2개 이상인 그룹에서 쌍 생성
        for bn, members in groups.items():
            if len(members) < 2:
                continue
            # short_id 숫자 기준 정렬 → 첫 번째가 base
            members.sort(key=lambda x: (len(x[0]), x[0]))
            base_sid, base_nm = members[0]
            for var_sid, var_nm in members[1:]:
                candidates.append({
                    "base_short_id": base_sid,
                    "base_name": base_nm,
                    "variant_short_id": var_sid,
                    "variant_name": var_nm,
                    "entity_type": singular,
                })

    return candidates


def extract_entity_relations(
    entities: Dict[str, List[Dict]],
    beat_shot_context: str,
    project_config: Optional[Dict] = None,
    opik_metadata: Optional[Dict] = None,
) -> Dict:
    """변형 관계를 추출하여 반환.

    1. 이름 기반 후보 자동 감지
    2. LLM이 후보 검증 + 추가 발견 + visual_similarity 판단
    """
    candidates = _detect_candidates(entities)

    # 후보 블록 생성
    if candidates:
        candidate_lines = []
        for c in candidates:
            candidate_lines.append(
                f"- [{c['entity_type']}] {c['base_short_id']} {c['base_name']} → "
                f"{c['variant_short_id']} {c['variant_name']}"
            )
        candidate_block = "\n".join(candidate_lines)
    else:
        candidate_block = "(코드가 감지한 후보 없음. 전체 목록에서 변형 관계를 직접 찾으세요.)"

    # 전체 요소 블록 (description 전체 전달 — 자르기 금지)
    entity_lines = []
    for etype in ["characters", "locations", "props"]:
        singular = _ETYPE_SINGULAR[etype]
        for e in entities.get(etype, []):
            desc = e.get("description", "")
            entity_lines.append(
                f"- [{singular}] {e.get('short_id', '')} {e.get('name', '')}: {desc}"
            )
    entity_block = "\n".join(entity_lines)

    if not entity_lines:
        logger.info("No entities to analyze for relations")
        return {"relations": [], "candidates_checked": 0, "relations_found": 0, "visual_similar_count": 0}

    system = load_prompt(_MODULE, "system")
    analyze_template = load_prompt(_MODULE, "analyze")
    schema = load_schema(_MODULE, "analyze_schema")

    user_prompt = analyze_template.format(
        candidate_block=candidate_block,
        entity_block=entity_block,
        context=beat_shot_context,
    )

    result = call_structured(
        step="entity_relation",
        system_prompt=system,
        user_prompt=user_prompt,
        response_schema=schema,
        project_config=project_config,
        schema_name="entity_relation",
        opik_metadata=opik_metadata,
    )

    relations = result.get("relations", [])

    # 유효성 검증: short_id가 실제 entity에 존재하는지
    known_sids = set()
    for etype in ["characters", "locations", "props"]:
        for e in entities.get(etype, []):
            sid = e.get("short_id", "")
            if sid:
                known_sids.add(sid)

    valid_relations = []
    for rel in relations:
        base_sid = rel.get("base_short_id", "")
        var_sid = rel.get("variant_short_id", "")
        if base_sid == var_sid:
            logger.warning("Dropping self-referencing relation: %s → %s", base_sid, var_sid)
            continue
        if base_sid in known_sids and var_sid in known_sids:
            valid_relations.append(rel)
        else:
            logger.warning(
                "Dropping relation with unknown short_id: %s → %s",
                base_sid, var_sid,
            )

    visual_similar_count = sum(1 for r in valid_relations if r.get("visual_similarity"))

    logger.info(
        "Entity relations: %d candidates → %d confirmed (%d visual_similar)",
        len(candidates), len(valid_relations), visual_similar_count,
    )

    return {
        "relations": valid_relations,
        "candidates_checked": len(candidates),
        "relations_found": len(valid_relations),
        "visual_similar_count": visual_similar_count,
    }
