"""KnowledgeGraph: entity-relation graph for project knowledge."""

from collections import deque
from datetime import datetime, timezone

from memotrail.storage import SQLiteStore
from memotrail.utils import get_logger

logger = get_logger("memotrail.core.graph")


class KnowledgeGraph:
    """Graph layer over SQLite entity and relation tables.

    Supports adding entities/relations, querying by type, BFS traversal,
    and natural language graph queries.
    """

    def __init__(self, sqlite_store: SQLiteStore | None = None):
        self.sqlite = sqlite_store or SQLiteStore()

    def add_entity(
        self,
        name: str,
        entity_type: str,
        session_id: str,
        description: str | None = None,
    ) -> str:
        """Add or update an entity.

        If the entity already exists (by name), increments mention_count
        and updates last_seen_session.

        Returns:
            Entity ID
        """
        now = datetime.now(timezone.utc).isoformat()
        normalized = name.lower().strip()

        # Check for existing entity
        row = self.sqlite.conn.execute(
            "SELECT id, mention_count FROM entities WHERE LOWER(name) = ?",
            (normalized,),
        ).fetchone()

        if row:
            self.sqlite.conn.execute(
                "UPDATE entities SET mention_count = ?, last_seen_session = ?, "
                "updated_at = ?, description = COALESCE(?, description) WHERE id = ?",
                (row["mention_count"] + 1, session_id, now, description, row["id"]),
            )
            self.sqlite.conn.commit()
            return row["id"]

        import uuid
        entity_id = f"ent_{uuid.uuid4().hex[:12]}"
        self.sqlite.conn.execute(
            "INSERT INTO entities (id, name, entity_type, description, "
            "first_seen_session, last_seen_session, mention_count, created_at, updated_at) "
            "VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)",
            (entity_id, name, entity_type, description, session_id, session_id, now, now),
        )
        self.sqlite.conn.commit()
        return entity_id

    def add_relation(
        self,
        source_entity_id: str,
        target_entity_id: str,
        relation_type: str,
        session_id: str,
        context: str | None = None,
        confidence: float = 1.0,
    ) -> str:
        """Add a relation between two entities.

        Skips if an identical relation already exists.

        Returns:
            Relation ID
        """
        # Check for existing identical relation
        existing = self.sqlite.conn.execute(
            "SELECT id FROM relations WHERE source_entity_id = ? AND target_entity_id = ? "
            "AND relation_type = ?",
            (source_entity_id, target_entity_id, relation_type),
        ).fetchone()
        if existing:
            return existing["id"]

        import uuid
        relation_id = f"rel_{uuid.uuid4().hex[:12]}"
        now = datetime.now(timezone.utc).isoformat()

        self.sqlite.conn.execute(
            "INSERT INTO relations (id, source_entity_id, target_entity_id, "
            "relation_type, context, session_id, confidence, created_at) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            (relation_id, source_entity_id, target_entity_id,
             relation_type, context, session_id, confidence, now),
        )
        self.sqlite.conn.commit()
        return relation_id

    def get_entity(self, name: str) -> dict | None:
        """Get an entity by name with its relations."""
        row = self.sqlite.conn.execute(
            "SELECT * FROM entities WHERE LOWER(name) = ?",
            (name.lower().strip(),),
        ).fetchone()
        if not row:
            return None

        entity = dict(row)
        entity["relations"] = self.get_entity_relations(row["id"])
        return entity

    def get_entity_by_id(self, entity_id: str) -> dict | None:
        """Get an entity by ID."""
        row = self.sqlite.conn.execute(
            "SELECT * FROM entities WHERE id = ?", (entity_id,)
        ).fetchone()
        return dict(row) if row else None

    def get_entity_relations(self, entity_id: str) -> list[dict]:
        """Get all relations for an entity (both as source and target)."""
        rows = self.sqlite.conn.execute(
            "SELECT r.*, "
            "  se.name as source_name, se.entity_type as source_type, "
            "  te.name as target_name, te.entity_type as target_type "
            "FROM relations r "
            "JOIN entities se ON r.source_entity_id = se.id "
            "JOIN entities te ON r.target_entity_id = te.id "
            "WHERE r.source_entity_id = ? OR r.target_entity_id = ?",
            (entity_id, entity_id),
        ).fetchall()
        return [dict(r) for r in rows]

    def get_entities_by_type(self, entity_type: str, limit: int = 50) -> list[dict]:
        """Get all entities of a given type."""
        rows = self.sqlite.conn.execute(
            "SELECT * FROM entities WHERE entity_type = ? "
            "ORDER BY mention_count DESC LIMIT ?",
            (entity_type, limit),
        ).fetchall()
        return [dict(r) for r in rows]

    def get_all_entities(self, limit: int = 100) -> list[dict]:
        """Get all entities ordered by mention count."""
        rows = self.sqlite.conn.execute(
            "SELECT * FROM entities ORDER BY mention_count DESC LIMIT ?",
            (limit,),
        ).fetchall()
        return [dict(r) for r in rows]

    def get_related_entities(self, entity_name: str, max_depth: int = 2) -> list[dict]:
        """BFS traversal to find related entities up to max_depth hops.

        Args:
            entity_name: Starting entity name
            max_depth: Maximum number of hops

        Returns:
            List of related entities with depth info
        """
        entity = self.get_entity(entity_name)
        if not entity:
            return []

        visited: set[str] = {entity["id"]}
        queue: deque[tuple[str, int]] = deque([(entity["id"], 0)])
        results: list[dict] = []

        while queue:
            current_id, depth = queue.popleft()
            if depth >= max_depth:
                continue

            relations = self.get_entity_relations(current_id)
            for rel in relations:
                # Find the other end of the relation
                other_id = (
                    rel["target_entity_id"]
                    if rel["source_entity_id"] == current_id
                    else rel["source_entity_id"]
                )

                if other_id in visited:
                    continue
                visited.add(other_id)

                other_entity = self.get_entity_by_id(other_id)
                if other_entity:
                    other_entity["depth"] = depth + 1
                    other_entity["relation_type"] = rel["relation_type"]
                    results.append(other_entity)
                    queue.append((other_id, depth + 1))

        return results

    def query_graph(self, query: str) -> str:
        """Answer a natural language question about the knowledge graph.

        Supports queries like:
        - "What libraries are used?"
        - "What does X depend on?"
        - "What replaced Y?"

        Args:
            query: Natural language question

        Returns:
            Formatted answer string
        """
        query_lower = query.lower()

        # Pattern: "What <type> are used?" or "List all <type>"
        type_queries = {
            "library": ["libraries", "packages", "modules"],
            "framework": ["frameworks"],
            "language": ["languages"],
            "service": ["services", "databases", "tools", "infrastructure"],
            "file": ["files"],
        }

        for entity_type, keywords in type_queries.items():
            if any(kw in query_lower for kw in keywords):
                entities = self.get_entities_by_type(entity_type)
                if not entities:
                    return f"No {entity_type} entities found."
                lines = [f"### {entity_type.title()} Entities ({len(entities)})\n"]
                for e in entities:
                    desc = f" — {e['description']}" if e.get("description") else ""
                    lines.append(f"- **{e['name']}** (mentions: {e['mention_count']}){desc}")
                return "\n".join(lines)

        # Pattern: "What does X depend on?" or "What uses X?"
        for keyword, relation_type in [
            ("depend", "depends_on"), ("use", "uses"),
            ("replace", "replaces"), ("part of", "part_of"),
        ]:
            if keyword in query_lower:
                # Try to extract entity name from query
                entities = self.get_all_entities()
                for e in entities:
                    if e["name"].lower() in query_lower:
                        relations = self.get_entity_relations(e["id"])
                        filtered = [
                            r for r in relations
                            if r["relation_type"] == relation_type
                        ]
                        if not filtered:
                            return f"No '{relation_type}' relations found for {e['name']}."
                        lines = [f"### {e['name']} — {relation_type} relations\n"]
                        for r in filtered:
                            other = r["target_name"] if r["source_entity_id"] == e["id"] else r["source_name"]
                            lines.append(f"- {other}")
                        return "\n".join(lines)

        # Fallback: show all entities
        entities = self.get_all_entities(limit=20)
        if not entities:
            return "No entities in the knowledge graph yet."

        lines = ["### Knowledge Graph Overview\n"]
        by_type: dict[str, list] = {}
        for e in entities:
            by_type.setdefault(e["entity_type"], []).append(e)

        for etype, ents in sorted(by_type.items()):
            lines.append(f"\n**{etype.title()}** ({len(ents)}):")
            for e in ents[:10]:
                lines.append(f"  - {e['name']} (mentions: {e['mention_count']})")

        return "\n".join(lines)
