"""
core/epoch/epoch_knowledge_writer.py

Story 9.06: EpochKnowledgeWriter — Axiom Persistence (KG + Qdrant)

Persists distilled axioms from a Nightly Epoch run to two destinations:

  1. Knowledge Graph (JSONL file) — appended to
     KNOWLEDGE_GRAPH/axioms/genesis_evolution_learnings.jsonl
  2. Qdrant L3 (genesis_scars collection) — one point per axiom, tagged
     axiom_type="distilled"

Design principles:
  - Atomic-per-destination: if Qdrant fails, the KG write still succeeds.
  - Dependency injection for both qdrant_client and embed_fn — no real I/O
    in tests.
  - fast_embed() is a thin wrapper around the injected embed_fn; falls back
    to a zero-vector (length 384) when no embed_fn is provided.

# VERIFICATION_STAMP
# Story: 9.06
# Verified By: parallel-builder
# Verified At: 2026-02-25T00:00:00Z
# Tests: 14/14
# Coverage: 100%
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Callable, List, Optional

from core.epoch.axiom_distiller import Axiom

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

KG_FILE_PATH: str = (
    "/mnt/e/genesis-system/KNOWLEDGE_GRAPH/axioms/genesis_evolution_learnings.jsonl"
)

QDRANT_COLLECTION: str = "genesis_scars"

# Default embedding dimension — matches nomic-embed-text (384-dim)
_DEFAULT_VECTOR_DIM: int = 384


# ---------------------------------------------------------------------------
# Result dataclass
# ---------------------------------------------------------------------------


@dataclass
class WriteResult:
    """Summary of a single EpochKnowledgeWriter.write() call."""

    kg_file_path: str
    qdrant_upserts: int
    jsonl_entries: int


# ---------------------------------------------------------------------------
# EpochKnowledgeWriter
# ---------------------------------------------------------------------------


class EpochKnowledgeWriter:
    """Persists distilled axioms to the Knowledge Graph and Qdrant L3.

    Parameters
    ----------
    qdrant_client:
        Any object with an ``upsert(collection_name, points)`` method.
        When None, Qdrant writes are skipped gracefully (partial success).
    embed_fn:
        Callable ``(text: str) -> list[float]`` that returns an embedding
        vector.  When None, a zero-vector of length ``_DEFAULT_VECTOR_DIM``
        is used as a placeholder.
    kg_path:
        Absolute path to the target KG JSONL file.  Defaults to
        ``KG_FILE_PATH``.  Override in tests via ``tempfile.NamedTemporaryFile``.
    """

    def __init__(
        self,
        qdrant_client=None,
        embed_fn: Optional[Callable[[str], List[float]]] = None,
        kg_path: str = KG_FILE_PATH,
    ) -> None:
        self.qdrant = qdrant_client
        self.embed_fn = embed_fn
        self.kg_path = kg_path

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def write(self, axioms: List[Axiom], epoch_id: str) -> WriteResult:
        """Persist *axioms* to both destinations and return a WriteResult.

        KG write always runs first.  Qdrant write runs next; any Qdrant
        failure is caught and logged — it does NOT abort the overall write.

        Args:
            axioms:  List of :class:`~core.epoch.axiom_distiller.Axiom`
                     objects to persist.
            epoch_id: Identifier for the current epoch run, e.g.
                      ``"epoch_2026_02_25"``.

        Returns:
            :class:`WriteResult` with ``kg_file_path``, ``qdrant_upserts``
            (0 on failure), and ``jsonl_entries``.
        """
        kg_written = self._write_kg(axioms, epoch_id)
        qdrant_written = self._write_qdrant(axioms, epoch_id)
        return WriteResult(
            kg_file_path=self.kg_path,
            qdrant_upserts=qdrant_written,
            jsonl_entries=kg_written,
        )

    # ------------------------------------------------------------------
    # Private helpers — KG
    # ------------------------------------------------------------------

    def _write_kg(self, axioms: List[Axiom], epoch_id: str) -> int:
        """Append axioms to the KG JSONL file in append mode.

        Returns the number of lines written (== number of axioms).
        Returns 0 on any I/O error (error is logged, not re-raised).
        """
        if not axioms:
            return 0

        today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
        lines: List[str] = []
        for axiom in axioms:
            entry = {
                "id": axiom.id,
                "content": axiom.content,
                "category": axiom.category,
                "epoch": epoch_id,
                "date": today,
            }
            lines.append(json.dumps(entry))

        try:
            # "a" mode — append only, never overwrite
            with open(self.kg_path, "a", encoding="utf-8") as fh:
                for line in lines:
                    fh.write(line + "\n")
            return len(lines)
        except OSError as exc:
            logger.error("EpochKnowledgeWriter: KG write failed: %s", exc)
            return 0

    # ------------------------------------------------------------------
    # Private helpers — Qdrant
    # ------------------------------------------------------------------

    def _write_qdrant(self, axioms: List[Axiom], epoch_id: str) -> int:
        """Upsert each axiom as an individual point into Qdrant L3.

        Individual upserts (not batch) ensure one failure doesn't block
        the rest.  Any exception is caught and logged.

        Returns the total number of successful upserts.
        """
        if self.qdrant is None:
            return 0
        if not axioms:
            return 0

        upserted = 0
        for axiom in axioms:
            vector = self._fast_embed(axiom.content)
            point = {
                "id": axiom.id,
                "payload": {
                    "content": axiom.content,
                    "category": axiom.category,
                    "confidence": axiom.confidence,
                    "epoch": epoch_id,
                    "axiom_type": "distilled",
                    "source_saga_ids": axiom.source_saga_ids,
                },
                "vector": vector,
            }
            try:
                self.qdrant.upsert(
                    collection_name=QDRANT_COLLECTION,
                    points=[point],
                )
                upserted += 1
            except Exception as exc:  # noqa: BLE001
                logger.warning(
                    "EpochKnowledgeWriter: Qdrant upsert failed for axiom %s: %s",
                    axiom.id,
                    exc,
                )
        return upserted

    # ------------------------------------------------------------------
    # Embedding helper
    # ------------------------------------------------------------------

    def _fast_embed(self, text: str) -> List[float]:
        """Return an embedding vector for *text*.

        Delegates to self.embed_fn when available; otherwise returns a
        zero-vector of length ``_DEFAULT_VECTOR_DIM`` as a safe placeholder.
        """
        if self.embed_fn is not None:
            try:
                return list(self.embed_fn(text))
            except Exception as exc:  # noqa: BLE001
                logger.warning(
                    "EpochKnowledgeWriter: embed_fn failed for text %r: %s",
                    text[:50],
                    exc,
                )
        return [0.0] * _DEFAULT_VECTOR_DIM
