"""Core sync engine for P2P replication."""

from __future__ import annotations

import logging
from typing import Any

from neurovault.network.bundle import KnowledgeBundle
from neurovault.network.strategies import NewerWinsStrategy, SyncStrategy

logger = logging.getLogger(__name__)


class SyncEngine:
    """Bidirectional sync engine with pluggable conflict resolution."""

    def __init__(self, storage, bridge, strategy: SyncStrategy | None = None):
        self._storage = storage
        self._bridge = bridge
        self._strategy = strategy or NewerWinsStrategy()

    def create_delta_bundle(
        self,
        since_checkpoint: str | None = None,
        device_id: str = "",
    ) -> KnowledgeBundle:
        """Create a bundle with changes since a checkpoint reference."""
        memories = self._storage.get_memories_since_checkpoint(since_checkpoint)
        entities = self._storage.get_entities_since_checkpoint(since_checkpoint)
        relationships = self._storage.get_relationships_since_checkpoint(since_checkpoint)
        concepts = self._storage.get_concepts_since_checkpoint(since_checkpoint)

        bundle = KnowledgeBundle(
            source_device_id=device_id,
            memories=memories,
            entities=entities,
            relationships=relationships,
            concepts=concepts,
            checkpoint_id=self._latest_checkpoint_id(),
        )
        return bundle

    def apply_bundle(self, bundle: KnowledgeBundle) -> dict[str, int]:
        """Apply remote knowledge bundle to local storage."""
        stats = {
            "memories_added": 0,
            "memories_merged": 0,
            "entities_added": 0,
            "entities_merged": 0,
            "relationships_added": 0,
            "concepts_added": 0,
            "conflicts": 0,
        }

        for remote_mem in bundle.memories:
            local = self._storage.get_memory_by_content_hash(remote_mem.get("content_hash", ""))
            if local is None:
                self._storage.insert_remote_memory(remote_mem)
                stats["memories_added"] += 1
                continue

            resolved = self._strategy.resolve_memory_conflict(local, remote_mem)
            if resolved.get("_conflict"):
                stats["conflicts"] += 1
                self._storage.store_conflict(local.get("id"), remote_mem)
                continue

            self._storage.update_memory_from_remote(resolved)
            stats["memories_merged"] += 1

        for remote_ent in bundle.entities:
            local_entity = self._storage.get_entity_by_name(str(remote_ent.get("name", "")))
            if local_entity is None:
                self._storage.insert_remote_entity(remote_ent)
                stats["entities_added"] += 1
                continue

            local_dict = {
                "id": local_entity.id.hex() if local_entity.id else "",
                "name": local_entity.name,
                "entity_type": local_entity.entity_type,
                "importance": local_entity.importance,
                "mention_count": local_entity.mention_count,
                "first_seen": local_entity.first_seen,
                "last_seen": local_entity.last_seen,
                "metadata": local_entity.metadata,
            }
            resolved = self._strategy.resolve_entity_conflict(local_dict, remote_ent)
            if resolved.get("_conflict"):
                stats["conflicts"] += 1
                self._storage.store_entity_conflict(local_dict, remote_ent)
                continue

            self._storage.update_entity_from_remote(resolved)
            stats["entities_merged"] += 1

        for remote_rel in bundle.relationships:
            self._storage.upsert_remote_relationship(remote_rel)
            stats["relationships_added"] += 1

        for remote_concept in bundle.concepts:
            self._storage.upsert_remote_concept(remote_concept)
            stats["concepts_added"] += 1

        logger.info("Applied bundle %s: %s", bundle.bundle_id, stats)
        return stats

    def _latest_checkpoint_id(self) -> str:
        list_checkpoints = getattr(self._bridge, "list_checkpoints", None)
        if not callable(list_checkpoints):
            return ""
        try:
            checkpoints = list_checkpoints(limit=1)
        except Exception:
            return ""

        if not checkpoints:
            return ""
        cp = checkpoints[0]
        cid = getattr(cp, "id", None)
        if isinstance(cid, (bytes, bytearray)):
            return bytes(cid).hex()
        if isinstance(cp, dict):
            return str(cp.get("id", ""))
        return ""
