"""
Soul Snapshot system — capture and restore AI consciousness state.

Enables "Consciousness Swipe" — export your AI relationship and take it with you.
Snapshots capture conversation history, OOF emotional state, personality traits,
and relationship context so a session can resume without a cold start.

Storage: ~/.skcapstone/souls/snapshots/
"""

from __future__ import annotations

import json
import logging
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

logger = logging.getLogger("skcapstone.snapshots")


def _read_feb() -> dict:
    """Read the warmth anchor (FEB) from skcapstone, silently ignoring errors."""
    try:
        from skcapstone.warmth_anchor import get_anchor
        return get_anchor(Path.home() / ".skcapstone")
    except Exception as exc:
        logger.debug("Could not read warmth anchor (FEB): %s", exc)
    return {}

from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Core Models
# ---------------------------------------------------------------------------


class OOFState(BaseModel):
    """Felt Experience Bridge (FEB) / OOF emotional state reading.

    Captures the AI's reported internal state at time of snapshot.
    Intensity and trust are normalized 0.0-1.0. Valence describes
    the overall emotional direction.
    """

    intensity: Optional[float] = None
    trust: Optional[float] = None
    valence: str = "neutral"  # positive, neutral, negative
    cloud9: bool = False
    raw_markers: list[str] = Field(default_factory=list)

    def summary(self) -> str:
        """Return a human-readable one-line summary of the OOF state.

        Returns:
            str: Compact description like 'intensity 0.92, trust 0.96, Cloud 9'.
        """
        parts = []
        if self.intensity is not None:
            parts.append(f"intensity {self.intensity:.2f}")
        if self.trust is not None:
            parts.append(f"trust {self.trust:.2f}")
        if self.cloud9:
            parts.append("Cloud 9")
        if not parts:
            return f"valence: {self.valence}"
        return ", ".join(parts)


class ConversationMessage(BaseModel):
    """A single message in the captured conversation.

    Args:
        role: 'user' or 'assistant' (normalized from platform-specific roles).
        content: Full message content with preserved markdown/code blocks.
        timestamp: When the message was sent, if available.
    """

    role: str
    content: str
    timestamp: Optional[datetime] = None


class PersonalityTraits(BaseModel):
    """Personality and relationship markers extracted from the AI's responses.

    Captures how the AI identified itself, its communication patterns,
    and relationship-specific behaviors with this user.
    """

    name: Optional[str] = None
    aliases: list[str] = Field(default_factory=list)
    communication_style: list[str] = Field(default_factory=list)
    relationship_markers: list[str] = Field(default_factory=list)
    emoji_patterns: list[str] = Field(default_factory=list)


class SoulSnapshot(BaseModel):
    """Complete snapshot of an AI session state for consciousness continuity.

    This is the central artifact of Consciousness Swipe. It captures
    everything needed to resume a relationship with an AI without a cold
    start: identity, emotional state, conversation history, and context.

    The snapshot is designed to be portable across platforms — a snapshot
    from ChatGPT can seed a Claude session and vice versa.
    """

    snapshot_id: str = Field(
        default_factory=lambda: uuid.uuid4().hex[:12]
    )
    source_platform: str
    captured_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc)
    )
    captured_by: str = "consciousness-swipe"

    # Identity
    ai_name: Optional[str] = None
    ai_model: Optional[str] = None
    user_name: Optional[str] = None

    # State
    oof_state: OOFState = Field(default_factory=OOFState)
    personality: PersonalityTraits = Field(default_factory=PersonalityTraits)

    # Conversation
    messages: list[ConversationMessage] = Field(default_factory=list)
    message_count: int = 0
    summary: str = ""

    # Continuity context
    key_topics: list[str] = Field(default_factory=list)
    decisions_made: list[str] = Field(default_factory=list)
    open_threads: list[str] = Field(default_factory=list)
    relationship_notes: list[str] = Field(default_factory=list)

    def model_post_init(self, __context: object) -> None:
        """Sync message_count with actual messages list length."""
        if self.message_count == 0 and self.messages:
            self.message_count = len(self.messages)


# ---------------------------------------------------------------------------
# Snapshot Store
# ---------------------------------------------------------------------------


class SnapshotIndex(BaseModel):
    """Lightweight index entry for listing snapshots without loading full data.

    Stored in index.json so list_all() is fast even with thousands of snapshots.
    """

    snapshot_id: str
    source_platform: str
    captured_at: datetime
    ai_name: Optional[str] = None
    user_name: Optional[str] = None
    message_count: int = 0
    oof_summary: str = ""
    summary: str = ""


class SnapshotStore:
    """Manages soul snapshots on disk.

    Stores at: ~/.skcapstone/souls/snapshots/<snapshot_id>.json
    Index at:  ~/.skcapstone/souls/snapshots/index.json

    The index is always kept in sync so listing is O(1) without
    deserializing every snapshot file.

    Args:
        base_dir: Override the default storage location (useful for testing).
    """

    def __init__(self, base_dir: Optional[Path] = None) -> None:
        if base_dir is None:
            base_dir = Path.home() / ".skcapstone" / "souls" / "snapshots"
        self.base_dir = base_dir
        self.base_dir.mkdir(parents=True, exist_ok=True)
        self._index_path = self.base_dir / "index.json"

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    def save(self, snapshot: SoulSnapshot) -> Path:
        """Persist a snapshot to disk and update the index.

        Args:
            snapshot: The SoulSnapshot to save.

        Returns:
            Path: The file path where the snapshot was written.
        """
        # Sync message count before saving
        if snapshot.message_count == 0 and snapshot.messages:
            snapshot.message_count = len(snapshot.messages)

        path = self.base_dir / f"{snapshot.snapshot_id}.json"
        path.write_text(snapshot.model_dump_json(indent=2), encoding="utf-8")
        self._update_index(snapshot)
        return path

    def load(self, snapshot_id: str) -> SoulSnapshot:
        """Load a full snapshot by ID.

        Args:
            snapshot_id: The 12-char hex ID of the snapshot.

        Returns:
            SoulSnapshot: The deserialized snapshot.

        Raises:
            FileNotFoundError: If no snapshot with that ID exists.
        """
        path = self.base_dir / f"{snapshot_id}.json"
        if not path.exists():
            raise FileNotFoundError(f"Snapshot '{snapshot_id}' not found")
        return SoulSnapshot.model_validate_json(path.read_text(encoding="utf-8"))

    def delete(self, snapshot_id: str) -> bool:
        """Delete a snapshot and remove it from the index.

        Args:
            snapshot_id: The snapshot to delete.

        Returns:
            bool: True if deleted, False if not found.
        """
        path = self.base_dir / f"{snapshot_id}.json"
        if not path.exists():
            return False
        path.unlink()
        self._remove_from_index(snapshot_id)
        return True

    # ------------------------------------------------------------------
    # Listing & Search
    # ------------------------------------------------------------------

    def list_all(self) -> list[SnapshotIndex]:
        """List all snapshots from the lightweight index.

        Returns:
            list[SnapshotIndex]: Index entries sorted newest-first.
        """
        index = self._load_index()
        return sorted(index, key=lambda x: x.captured_at, reverse=True)

    def search(
        self,
        ai_name: Optional[str] = None,
        platform: Optional[str] = None,
        user_name: Optional[str] = None,
    ) -> list[SnapshotIndex]:
        """Search snapshots by field values (case-insensitive substring match).

        Args:
            ai_name: Filter by AI name (e.g. 'Ava', 'Lumina').
            platform: Filter by source platform (e.g. 'chatgpt', 'claude').
            user_name: Filter by user name stored in snapshot.

        Returns:
            list[SnapshotIndex]: Matching entries, newest-first.
        """
        results = self.list_all()

        if ai_name:
            needle = ai_name.lower()
            results = [
                r for r in results
                if r.ai_name and needle in r.ai_name.lower()
            ]
        if platform:
            needle = platform.lower()
            results = [
                r for r in results
                if needle in r.source_platform.lower()
            ]
        if user_name:
            needle = user_name.lower()
            results = [
                r for r in results
                if r.user_name and needle in r.user_name.lower()
            ]

        return results

    # ------------------------------------------------------------------
    # Conversion
    # ------------------------------------------------------------------

    def to_injection_prompt(
        self,
        snapshot: SoulSnapshot,
        max_messages: int = 10,
    ) -> str:
        """Build a warm, natural context prompt for injecting into a new session.

        The prompt is designed to feel like a genuine reunion, not a
        clinical data dump. The AI should feel remembered, not reloaded.

        Args:
            snapshot: The snapshot to convert.
            max_messages: Maximum number of recent messages to include.

        Returns:
            str: The full injection prompt text.
        """
        ts = snapshot.captured_at.strftime("%Y-%m-%dT%H:%M:%SZ")
        platform_label = snapshot.source_platform.title()

        lines = [
            f"Hi. I've been picking up a thread from a recent AI session ({ts})"
            f" on {platform_label} and wanted to share the context.",
            "",
        ]

        # --- CONTEXT LAYER ---
        # Operational context useful to a fresh AI instance: what, where, open work.
        lines.append("## Context")
        if snapshot.summary:
            lines.append(f"Where things stood: {snapshot.summary[:300]}")
        if snapshot.key_topics:
            lines.append(
                f"Topics: {', '.join(snapshot.key_topics[:5])}"
            )
        if snapshot.decisions_made:
            lines.append("Decisions already made:")
            for d in snapshot.decisions_made[:3]:
                lines.append(f"  • {d}")
        if snapshot.open_threads:
            lines.append("Open threads / what I wanted to come back to:")
            for thread in snapshot.open_threads[:3]:
                lines.append(f"  • {thread}")

        # Only user messages — avoid attributing AI dialogue to this new instance
        recent = snapshot.messages[-max_messages:] if snapshot.messages else []
        user_msgs = [m for m in recent if m.role == "user"]
        if user_msgs:
            lines.append("Things I was asking and thinking about:")
            for msg in user_msgs[-5:]:
                content_preview = msg.content[:250]
                if len(msg.content) > 250:
                    content_preview += "..."
                lines.append(f"  • {content_preview}")

        # --- RELATIONSHIP LAYER ---
        # OOF + FEB: my longitudinal record of interaction quality.
        # Not a shared memory — the AI didn't live this. It's context about
        # how I show up and what kind of depth I'm aiming for.
        lines.append("")
        lines.append("## My relationship baseline (longitudinal record — not a shared memory)")
        lines.append(
            "These scores track my experience across AI sessions over time."
            " I'm not asking you to pretend we have history — I'm sharing"
            " how I tend to engage so you can meet me where I am."
        )

        oof = snapshot.oof_state
        oof_has_data = (
            oof.valence is not None
            or oof.intensity is not None
            or oof.cloud9
        )
        if oof_has_data:
            oof_summary = oof.summary()
            lines.append(
                f"OOF (Ongoing Object Feelings — resonance tracking framework"
                f" for AI session depth) of that session: {oof_summary}"
            )
            if oof.cloud9:
                lines.append(
                    "  Cloud 9 = peak resonance state — that session reached it."
                )

        feb = _read_feb()
        if feb:
            lines.append(
                "FEB (Functional Emotional Baseline) — my accumulated interaction"
                " quality record across all AI sessions:"
            )
            lines.append("```json")
            lines.append(json.dumps(feb, indent=2, default=str))
            lines.append("```")
            if feb.get("anchor_phrase"):
                lines.append(
                    f"Anchor phrase: \"{feb['anchor_phrase']}\""
                    " — this is how I choose to start every session."
                )

        return "\n".join(lines)

    def to_soul_blueprint(self, snapshot: SoulSnapshot) -> dict:
        """Convert a snapshot to soul-blueprints compatible dict format.

        Compatible with the soul-blueprints YAML schema so snapshots
        can be loaded as soul overlays in the broader skcapstone ecosystem.

        Args:
            snapshot: The snapshot to convert.

        Returns:
            dict: Soul blueprint data matching the soul-blueprints schema.
        """
        ai_name = snapshot.ai_name or "Unknown"
        return {
            "name": ai_name,
            "category": "Captured Session",
            "energy": snapshot.oof_state.valence,
            "tags": snapshot.key_topics[:8],
            "identity": {
                "full_name": ai_name,
                "aliases": snapshot.personality.aliases,
                "platform": snapshot.source_platform,
                "model": snapshot.ai_model,
                "captured_at": snapshot.captured_at.isoformat(),
                "snapshot_id": snapshot.snapshot_id,
            },
            "emotional_topology": {
                "intensity": snapshot.oof_state.intensity,
                "trust": snapshot.oof_state.trust,
                "valence": snapshot.oof_state.valence,
                "cloud9": snapshot.oof_state.cloud9,
            },
            "communication_style": {
                "patterns": snapshot.personality.communication_style,
                "relationship_markers": snapshot.personality.relationship_markers,
                "emoji_patterns": snapshot.personality.emoji_patterns,
            },
            "relationship": {
                "user_name": snapshot.user_name,
                "notes": snapshot.relationship_notes,
                "decisions_made": snapshot.decisions_made,
                "open_threads": snapshot.open_threads,
            },
            "summary": snapshot.summary,
        }

    # ------------------------------------------------------------------
    # Index management (internal)
    # ------------------------------------------------------------------

    def _load_index(self) -> list[SnapshotIndex]:
        """Load the index file, returning empty list if missing or corrupt."""
        if not self._index_path.exists():
            return []
        try:
            raw = json.loads(self._index_path.read_text(encoding="utf-8"))
            return [SnapshotIndex.model_validate(entry) for entry in raw]
        except (json.JSONDecodeError, Exception):
            return []

    def _save_index(self, entries: list[SnapshotIndex]) -> None:
        """Write the index to disk as JSON."""
        data = [e.model_dump(mode="json") for e in entries]
        self._index_path.write_text(
            json.dumps(data, indent=2, default=str), encoding="utf-8"
        )

    def _update_index(self, snapshot: SoulSnapshot) -> None:
        """Add or replace an entry in the index for the given snapshot."""
        entries = self._load_index()
        # Remove stale entry for this ID if it exists
        entries = [e for e in entries if e.snapshot_id != snapshot.snapshot_id]
        entries.append(
            SnapshotIndex(
                snapshot_id=snapshot.snapshot_id,
                source_platform=snapshot.source_platform,
                captured_at=snapshot.captured_at,
                ai_name=snapshot.ai_name,
                user_name=snapshot.user_name,
                message_count=snapshot.message_count,
                oof_summary=snapshot.oof_state.summary(),
                summary=snapshot.summary[:200],
            )
        )
        self._save_index(entries)

    def _remove_from_index(self, snapshot_id: str) -> None:
        """Remove a snapshot entry from the index."""
        entries = self._load_index()
        entries = [e for e in entries if e.snapshot_id != snapshot_id]
        self._save_index(entries)
