"""
knowledge_graph.py
Phase 2: InsightV2 기반 옵시디언 스타일 지식 그래프 빌더
작성자: 루 (개발3팀)
"""

from __future__ import annotations

import json
import pathlib
from collections import defaultdict


class KnowledgeGraph:
    """인사이트 간 관계를 관리하는 지식 그래프 빌더"""

    # 엣지 weight 상수
    WEIGHT_SHARED_TAG = 0.8
    WEIGHT_SAME_CATEGORY = 0.5
    WEIGHT_RELATED_TOPIC = 0.7
    WEIGHT_SAME_EXPERT = 0.3

    def __init__(self, insights_dir: str, graph_path: str, index_path: str):
        """
        Args:
            insights_dir: 마크다운 인사이트 파일 저장 경로
            graph_path: graph.json 저장 경로
            index_path: insights_index.json 저장 경로
        """
        self.insights_dir = insights_dir
        self.graph_path = graph_path
        self.index_path = index_path

        # 내부 상태 (build 후 채워짐)
        self._graph: dict = {}
        self._index: dict = {}

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def build_from_insights(self, insights: list[dict]) -> dict:
        """InsightV2 dict 리스트로부터 전체 그래프를 구축한다.

        1. 각 인사이트를 마크다운 파일로 저장 (insights/ 디렉토리)
        2. 자동 연결 로직으로 edge 생성
        3. graph.json 저장
        4. insights_index.json 저장

        Returns:
            {"nodes": int, "edges": int, "categories": dict[str, int]}
        """
        # insights_dir 생성
        pathlib.Path(self.insights_dir).mkdir(parents=True, exist_ok=True)

        # 1. 마크다운 파일 저장
        for insight in insights:
            self._save_insight_as_markdown(insight)

        # 2. 엣지 생성
        edges = self._build_edges(insights)

        # 3. graph.json 저장
        self._graph = self._build_graph_json(insights, edges)
        pathlib.Path(self.graph_path).parent.mkdir(parents=True, exist_ok=True)
        pathlib.Path(self.graph_path).write_text(
            json.dumps(self._graph, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )

        # 4. insights_index.json 저장
        self._index = self._build_index(insights)
        pathlib.Path(self.index_path).parent.mkdir(parents=True, exist_ok=True)
        pathlib.Path(self.index_path).write_text(
            json.dumps(self._index, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )

        # 카테고리별 카운트
        categories: dict[str, int] = defaultdict(int)
        for insight in insights:
            categories[insight.get("category", "기타")] += 1

        return {
            "nodes": len(insights),
            "edges": len(edges),
            "categories": dict(categories),
        }

    def get_related(self, insight_id: str, max_depth: int = 1) -> list[dict]:
        """특정 인사이트의 연결된 인사이트 목록 반환.

        max_depth=1: 직접 연결만
        max_depth=2: 2-hop 연결까지
        """
        if not self._graph:
            return []

        edges = self._graph.get("edges", [])
        nodes_by_id = {n["id"]: n for n in self._graph.get("nodes", [])}

        visited: set[str] = {insight_id}
        current_frontier: set[str] = {insight_id}
        result: list[dict] = []

        for _ in range(max_depth):
            next_frontier: set[str] = set()
            for edge in edges:
                frm = edge["from"]
                to = edge["to"]
                if frm in current_frontier and to not in visited:
                    next_frontier.add(to)
                elif to in current_frontier and frm not in visited:
                    next_frontier.add(frm)
            for nid in next_frontier:
                if nid in nodes_by_id:
                    result.append(nodes_by_id[nid])
                visited.add(nid)
            current_frontier = next_frontier

        return result

    def search_by_tag(self, tag: str) -> list[str]:
        """태그로 인사이트 ID 검색"""
        if not self._index:
            return []
        return list(self._index.get("by_tag", {}).get(tag, []))

    def search_by_category(self, category: str) -> list[str]:
        """카테고리로 인사이트 ID 검색"""
        if not self._index:
            return []
        return list(self._index.get("by_category", {}).get(category, []))

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _save_insight_as_markdown(self, insight: dict) -> pathlib.Path:
        """단일 인사이트를 마크다운 파일로 저장.

        파일 형식 (YAML frontmatter + 마크다운)
        """
        insight_id: str = insight.get("id", "unknown")
        title: str = insight.get("title", "")
        itype: str = insight.get("type", "")
        category: str = insight.get("category", "")
        tags: list = insight.get("tags", [])
        related: list = insight.get("related_topics", [])
        expert: str = insight.get("expert", "")
        confidence: str = insight.get("confidence", "medium")
        source_date: str = insight.get("source_date", "")
        summary: str = insight.get("summary", "")
        key_points: list = insight.get("key_points", [])
        question: str = insight.get("question", "")
        answer: str = insight.get("answer", "")

        # YAML frontmatter
        tags_yaml = "[" + ", ".join(tags) + "]"
        related_yaml = "[" + ", ".join(related) + "]"

        frontmatter_lines = [
            "---",
            f"id: {insight_id}",
            f"title: {title}",
            f"type: {itype}",
            f"category: {category}",
            f"tags: {tags_yaml}",
            f"related: {related_yaml}",
            f"expert: {expert}",
            f"confidence: {confidence}",
            f"source_date: {source_date}",
            "---",
        ]
        frontmatter = "\n".join(frontmatter_lines)

        # 본문
        body_lines: list[str] = []

        if itype == "qa":
            body_lines.append("\n## 질문")
            body_lines.append(question)
            body_lines.append("\n## 답변")
            # 답변에 태그 기반 위키링크 삽입 시도 (간단 버전: 태그를 [[태그]] 형식으로)
            answer_text = answer
            for tag in tags:
                if tag and tag in answer_text:
                    answer_text = answer_text.replace(tag, f"[[{tag}]]", 1)
            body_lines.append(answer_text)
        else:
            body_lines.append("\n## 요약")
            body_lines.append(summary)

        # 핵심 포인트
        body_lines.append("\n## 핵심 포인트")
        for point in key_points:
            body_lines.append(f"- {point}")

        # 관련 인사이트 (백링크) - related_topics 기반으로 임시, 실제 연결은 graph에서 나옴
        # 여기서는 related_topics를 위키링크로 표현
        if related:
            body_lines.append("\n## 관련 인사이트")
            for rel in related:
                body_lines.append(f"- [[{rel}]]")

        body = "\n".join(body_lines)
        content = frontmatter + "\n" + body + "\n"

        out_path = pathlib.Path(self.insights_dir) / f"{insight_id}.md"
        out_path.write_text(content, encoding="utf-8")
        return out_path

    def _build_edges(self, insights: list[dict]) -> list[dict]:
        """자동 연결 로직으로 edge를 생성한다.

        4가지 연결 방법:
        1. 태그 기반: 같은 태그를 가진 인사이트끼리 연결 (relation: "shared_tag")
        2. 카테고리 기반: 같은 카테고리/서브카테고리 연결 (relation: "same_category")
        3. 키워드 중복: related_topics 교집합 2개 이상이면 연결 (relation: "related_topic")
        4. 전문가 기반: 같은 expert가 답변한 인사이트 연결 (relation: "same_expert")

        중복 엣지 제거 (A→B와 B→A는 하나만 유지)
        """
        # 중복 방지용: (frozenset{id_a, id_b}, relation) → edge dict
        seen: dict[tuple, dict] = {}

        n = len(insights)
        for i in range(n):
            for j in range(i + 1, n):
                a = insights[i]
                b = insights[j]
                id_a = a["id"]
                id_b = b["id"]

                # 1. 태그 기반
                tags_a = set(a.get("tags", []))
                tags_b = set(b.get("tags", []))
                if tags_a & tags_b:
                    key = (frozenset([id_a, id_b]), "shared_tag")
                    if key not in seen:
                        seen[key] = {
                            "from": id_a,
                            "to": id_b,
                            "relation": "shared_tag",
                            "weight": self.WEIGHT_SHARED_TAG,
                        }

                # 2. 카테고리 기반
                cat_a = a.get("category", "")
                cat_b = b.get("category", "")
                if cat_a and cat_b and cat_a == cat_b:
                    key = (frozenset([id_a, id_b]), "same_category")
                    if key not in seen:
                        seen[key] = {
                            "from": id_a,
                            "to": id_b,
                            "relation": "same_category",
                            "weight": self.WEIGHT_SAME_CATEGORY,
                        }

                # 3. related_topics 교집합 2개 이상
                rt_a = set(a.get("related_topics", []))
                rt_b = set(b.get("related_topics", []))
                if len(rt_a & rt_b) >= 2:
                    key = (frozenset([id_a, id_b]), "related_topic")
                    if key not in seen:
                        seen[key] = {
                            "from": id_a,
                            "to": id_b,
                            "relation": "related_topic",
                            "weight": self.WEIGHT_RELATED_TOPIC,
                        }

                # 4. 전문가 기반
                expert_a = a.get("expert", "")
                expert_b = b.get("expert", "")
                if expert_a and expert_b and expert_a == expert_b:
                    key = (frozenset([id_a, id_b]), "same_expert")
                    if key not in seen:
                        seen[key] = {
                            "from": id_a,
                            "to": id_b,
                            "relation": "same_expert",
                            "weight": self.WEIGHT_SAME_EXPERT,
                        }

        return list(seen.values())

    def _build_graph_json(self, insights: list[dict], edges: list[dict]) -> dict:
        """graph.json 구조 생성."""
        nodes = [
            {
                "id": insight["id"],
                "title": insight.get("title", ""),
                "category": insight.get("category", ""),
                "type": insight.get("type", ""),
                "tags": insight.get("tags", []),
            }
            for insight in insights
        ]

        categories: dict[str, int] = defaultdict(int)
        types: dict[str, int] = defaultdict(int)
        for insight in insights:
            categories[insight.get("category", "기타")] += 1
            types[insight.get("type", "unknown")] += 1

        return {
            "nodes": nodes,
            "edges": edges,
            "stats": {
                "total_nodes": len(nodes),
                "total_edges": len(edges),
                "categories": dict(categories),
                "types": dict(types),
            },
        }

    def _build_index(self, insights: list[dict]) -> dict:
        """빠른 조회용 인덱스 생성."""
        by_id: dict[str, dict] = {}
        by_category: dict[str, list] = defaultdict(list)
        by_tag: dict[str, list] = defaultdict(list)
        by_expert: dict[str, list] = defaultdict(list)
        by_type: dict[str, list] = defaultdict(list)

        for insight in insights:
            iid = insight["id"]
            category = insight.get("category", "기타")
            expert = insight.get("expert", "")
            itype = insight.get("type", "unknown")

            by_id[iid] = {
                "title": insight.get("title", ""),
                "file": f"{iid}.md",
            }

            by_category[category].append(iid)

            for tag in insight.get("tags", []):
                by_tag[tag].append(iid)

            if expert:
                by_expert[expert].append(iid)

            by_type[itype].append(iid)

        return {
            "by_id": by_id,
            "by_category": dict(by_category),
            "by_tag": dict(by_tag),
            "by_expert": dict(by_expert),
            "by_type": dict(by_type),
        }
