"""
Brynq Global Entity Registry (Phase 60)
Central registry for all platform entities: agents, tasks, teams, workspaces,
services, workflows, blueprints. Supports relationships and graph traversal.

Storage:
  state/broker/registry/entities/{id}.json
  state/broker/registry/links.jsonl
"""

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from .server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

REGISTRY_DIR = BROKER_DIR / "registry"
ENTITIES_DIR = REGISTRY_DIR / "entities"
LINKS_FILE = REGISTRY_DIR / "links.jsonl"

VALID_ENTITY_TYPES = {"agent", "task", "team", "workspace", "service", "workflow", "blueprint"}
VALID_RELATIONSHIP_TYPES = {"owns", "member_of", "depends_on", "created_by", "assigned_to"}


def _ensure_dirs():
    ENTITIES_DIR.mkdir(parents=True, exist_ok=True)
    LINKS_FILE.parent.mkdir(parents=True, exist_ok=True)


def register_entity(entity_type: str, entity_id: str, name: str,
                    metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Register a new entity in the global registry.

    Args:
        entity_type: One of agent, task, team, workspace, service, workflow, blueprint.
        entity_id: Unique identifier for the entity.
        name: Human-readable name.
        metadata: Optional extra metadata dict.

    Returns:
        The created entity record.

    Raises:
        ValueError: If entity_type is invalid or entity_id already exists.
    """
    _ensure_dirs()

    if entity_type not in VALID_ENTITY_TYPES:
        raise ValueError(f"Invalid entity_type '{entity_type}'. Must be one of: {sorted(VALID_ENTITY_TYPES)}")

    path = ENTITIES_DIR / f"{entity_id}.json"
    if path.exists():
        raise ValueError(f"Entity '{entity_id}' already exists")

    entity = {
        "entity_id": entity_id,
        "entity_type": entity_type,
        "name": name,
        "metadata": metadata or {},
        "created_at": datetime.now(timezone.utc).isoformat(),
        "updated_at": datetime.now(timezone.utc).isoformat(),
    }
    _write_json_sync(path, entity)
    return entity


def get_entity(entity_id: str) -> Optional[Dict[str, Any]]:
    """Retrieve an entity by ID. Returns None if not found."""
    _ensure_dirs()
    path = ENTITIES_DIR / f"{entity_id}.json"
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def delete_entity(entity_id: str) -> bool:
    """Delete an entity and all its relationships. Returns True if deleted."""
    _ensure_dirs()
    path = ENTITIES_DIR / f"{entity_id}.json"
    if not path.exists():
        return False
    path.unlink()

    # Remove all links involving this entity
    if LINKS_FILE.exists():
        links = _read_jsonl_sync(LINKS_FILE)
        remaining = [
            link for link in links
            if link.get("entity_a") != entity_id and link.get("entity_b") != entity_id
        ]
        # Rewrite links file
        if remaining:
            LINKS_FILE.write_text("")
            for link in remaining:
                _append_jsonl_sync(LINKS_FILE, link)
        else:
            LINKS_FILE.write_text("")
    return True


def update_entity(entity_id: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    """Update fields on an existing entity. Returns updated entity or None."""
    _ensure_dirs()
    path = ENTITIES_DIR / f"{entity_id}.json"
    if not path.exists():
        return None
    try:
        entity = json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None

    # Don't allow overwriting core identity fields
    protected = {"entity_id", "created_at"}
    for key, value in updates.items():
        if key not in protected:
            entity[key] = value
    entity["updated_at"] = datetime.now(timezone.utc).isoformat()

    _write_json_sync(path, entity)
    return entity


def list_entities(entity_type: Optional[str] = None, query: Optional[str] = None,
                  limit: int = 50) -> List[Dict[str, Any]]:
    """List entities, optionally filtered by type and/or name search query."""
    _ensure_dirs()
    if not ENTITIES_DIR.exists():
        return []

    results = []
    for f in sorted(ENTITIES_DIR.iterdir()):
        if not f.suffix == ".json":
            continue
        try:
            entity = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue

        if entity_type and entity.get("entity_type") != entity_type:
            continue
        if query and query.lower() not in entity.get("name", "").lower():
            continue

        results.append(entity)
        if len(results) >= limit:
            break

    return results


def get_relationships(entity_id: str) -> List[Dict[str, Any]]:
    """Get all relationships involving an entity."""
    _ensure_dirs()
    if not LINKS_FILE.exists():
        return []

    links = _read_jsonl_sync(LINKS_FILE)
    return [
        link for link in links
        if link.get("entity_a") == entity_id or link.get("entity_b") == entity_id
    ]


def link_entities(entity_a: str, entity_b: str, relationship_type: str) -> Dict[str, Any]:
    """Create a relationship between two entities.

    Args:
        entity_a: Source entity ID.
        entity_b: Target entity ID.
        relationship_type: One of owns, member_of, depends_on, created_by, assigned_to.

    Returns:
        The created link record.

    Raises:
        ValueError: If relationship_type is invalid or either entity doesn't exist.
    """
    _ensure_dirs()

    if relationship_type not in VALID_RELATIONSHIP_TYPES:
        raise ValueError(
            f"Invalid relationship_type '{relationship_type}'. "
            f"Must be one of: {sorted(VALID_RELATIONSHIP_TYPES)}"
        )

    # Verify both entities exist
    if not (ENTITIES_DIR / f"{entity_a}.json").exists():
        raise ValueError(f"Entity '{entity_a}' not found")
    if not (ENTITIES_DIR / f"{entity_b}.json").exists():
        raise ValueError(f"Entity '{entity_b}' not found")

    # Check for duplicate link
    existing = _read_jsonl_sync(LINKS_FILE) if LINKS_FILE.exists() else []
    for link in existing:
        if (link.get("entity_a") == entity_a and
                link.get("entity_b") == entity_b and
                link.get("relationship_type") == relationship_type):
            return link  # Already exists, return it

    link = {
        "link_id": uuid.uuid4().hex[:12],
        "entity_a": entity_a,
        "entity_b": entity_b,
        "relationship_type": relationship_type,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    _append_jsonl_sync(LINKS_FILE, link)
    return link


def unlink_entities(entity_a: str, entity_b: str) -> bool:
    """Remove all relationships between two entities. Returns True if any removed."""
    _ensure_dirs()
    if not LINKS_FILE.exists():
        return False

    links = _read_jsonl_sync(LINKS_FILE)
    remaining = [
        link for link in links
        if not (
            (link.get("entity_a") == entity_a and link.get("entity_b") == entity_b) or
            (link.get("entity_a") == entity_b and link.get("entity_b") == entity_a)
        )
    ]

    if len(remaining) == len(links):
        return False  # Nothing removed

    LINKS_FILE.write_text("")
    for link in remaining:
        _append_jsonl_sync(LINKS_FILE, link)
    return True


def get_entity_graph(entity_id: str, depth: int = 2) -> Dict[str, Any]:
    """Return entity and connected entities up to N depth.

    Returns:
        Dict with 'root' entity, 'nodes' dict of entity_id -> entity,
        and 'edges' list of relationship dicts.
    """
    _ensure_dirs()
    root = get_entity(entity_id)
    if root is None:
        return {"root": None, "nodes": {}, "edges": []}

    nodes: Dict[str, Dict] = {entity_id: root}
    edges: List[Dict] = []
    visited: set = {entity_id}
    frontier: set = {entity_id}

    all_links = _read_jsonl_sync(LINKS_FILE) if LINKS_FILE.exists() else []

    for _ in range(depth):
        next_frontier: set = set()
        for eid in frontier:
            for link in all_links:
                other = None
                if link.get("entity_a") == eid:
                    other = link.get("entity_b")
                elif link.get("entity_b") == eid:
                    other = link.get("entity_a")

                if other and other not in visited:
                    visited.add(other)
                    next_frontier.add(other)
                    entity = get_entity(other)
                    if entity:
                        nodes[other] = entity
                    edges.append(link)
                elif other and other in visited:
                    # Still record the edge even if node already visited
                    if link not in edges:
                        edges.append(link)
        frontier = next_frontier
        if not frontier:
            break

    return {"root": root, "nodes": nodes, "edges": edges}


def get_stats() -> Dict[str, Any]:
    """Get counts of entities by type and total relationships."""
    _ensure_dirs()
    type_counts: Dict[str, int] = {}
    total_entities = 0

    if ENTITIES_DIR.exists():
        for f in ENTITIES_DIR.iterdir():
            if not f.suffix == ".json":
                continue
            try:
                entity = json.loads(f.read_text("utf-8"))
                etype = entity.get("entity_type", "unknown")
                type_counts[etype] = type_counts.get(etype, 0) + 1
                total_entities += 1
            except (json.JSONDecodeError, OSError):
                continue

    total_links = 0
    if LINKS_FILE.exists():
        total_links = len(_read_jsonl_sync(LINKS_FILE))

    return {
        "total_entities": total_entities,
        "total_relationships": total_links,
        "by_type": type_counts,
    }
