"""Core types and data models for Mnemosynth."""

from __future__ import annotations

import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from enum import Enum
from typing import Optional


class MemoryType(str, Enum):
    """Three memory types mirroring human cognitive architecture."""
    EPISODIC = "episodic"       # Timestamped events & conversations (hippocampus)
    SEMANTIC = "semantic"       # Facts, relationships, knowledge (neocortex)
    PROCEDURAL = "procedural"   # Tools, schemas, workflows (cerebellum)


class MemoryStatus(str, Enum):
    """Lifecycle status of a memory."""
    ACTIVE = "active"
    DEPRECATED = "deprecated"
    QUARANTINED = "quarantined"


class ExtractionMethod(str, Enum):
    """How the memory was extracted."""
    USER_STATEMENT = "user_statement"
    LLM_EXTRACTION = "llm_extraction"
    API_DATA = "api_data"
    IMPLICIT_CONTEXT = "implicit_context"
    DREAM_CONSOLIDATION = "dream_consolidation"


# Source reliability scores
SOURCE_RELIABILITY = {
    ExtractionMethod.USER_STATEMENT: 0.95,
    ExtractionMethod.LLM_EXTRACTION: 0.70,
    ExtractionMethod.API_DATA: 0.80,
    ExtractionMethod.IMPLICIT_CONTEXT: 0.50,
    ExtractionMethod.DREAM_CONSOLIDATION: 0.85,
}


@dataclass
class MemorySource:
    """Provenance tracking — who said it, when, how it was extracted."""
    session_id: str = ""
    extraction_method: ExtractionMethod = ExtractionMethod.USER_STATEMENT
    original_text: str = ""
    model_used: str = ""
    reliability: float = 0.95

    def to_dict(self) -> dict:
        return {
            "session_id": self.session_id,
            "extraction_method": self.extraction_method.value,
            "original_text": self.original_text,
            "model_used": self.model_used,
            "reliability": self.reliability,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "MemorySource":
        return cls(
            session_id=data.get("session_id", ""),
            extraction_method=ExtractionMethod(data.get("extraction_method", "user_statement")),
            original_text=data.get("original_text", ""),
            model_used=data.get("model_used", ""),
            reliability=data.get("reliability", 0.95),
        )


@dataclass
class MemoryNode:
    """A single memory unit — the fundamental building block of Mnemosynth.

    Every memory carries:
    - Content: what was remembered
    - Type: episodic / semantic / procedural
    - Confidence: 0.0 to 1.0 (decays over time)
    - Provenance: full audit trail
    - Temporal: when created, last accessed
    - Relations: supersedes / superseded_by chains
    """
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    content: str = ""
    memory_type: MemoryType = MemoryType.SEMANTIC
    confidence: float = 0.85
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    last_accessed: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    access_count: int = 0
    source: MemorySource = field(default_factory=MemorySource)
    status: MemoryStatus = MemoryStatus.ACTIVE
    sentiment_score: float = 0.0  # -1.0 to 1.0
    valid_from: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    valid_until: Optional[datetime] = None
    supersedes: Optional[str] = None
    superseded_by: Optional[str] = None
    corroboration_count: int = 0
    embedding: Optional[list[float]] = None
    tags: list[str] = field(default_factory=list)

    @classmethod
    def create(
        cls,
        content: str,
        memory_type: MemoryType = MemoryType.SEMANTIC,
        confidence: float = 0.85,
        source: MemorySource | None = None,
        tags: list[str] | None = None,
    ) -> "MemoryNode":
        """Factory for creating a new memory with sensible defaults."""
        now = datetime.now(timezone.utc)
        return cls(
            id=str(uuid.uuid4()),
            content=content,
            memory_type=memory_type,
            confidence=confidence,
            created_at=now,
            last_accessed=now,
            access_count=0,
            source=source or MemorySource(),
            status=MemoryStatus.ACTIVE,
            valid_from=now,
            tags=tags or [],
        )

    def touch(self) -> None:
        """Mark this memory as accessed (resets decay clock)."""
        self.last_accessed = datetime.now(timezone.utc)
        self.access_count += 1

    def deprecate(self, superseded_by_id: str | None = None) -> None:
        """Mark memory as deprecated (soft delete)."""
        self.status = MemoryStatus.DEPRECATED
        self.valid_until = datetime.now(timezone.utc)
        if superseded_by_id:
            self.superseded_by = superseded_by_id

    def quarantine(self) -> None:
        """Quarantine a suspicious memory."""
        self.status = MemoryStatus.QUARANTINED

    def corroborate(self) -> None:
        """Increment corroboration count and boost confidence."""
        self.corroboration_count += 1
        self.confidence = min(1.0, self.confidence + 0.05)
        self.touch()

    def to_dict(self) -> dict:
        """Serialize to dictionary for storage."""
        return {
            "id": self.id,
            "content": self.content,
            "memory_type": self.memory_type.value,
            "confidence": self.confidence,
            "created_at": self.created_at.isoformat(),
            "last_accessed": self.last_accessed.isoformat(),
            "access_count": self.access_count,
            "source": self.source.to_dict(),
            "status": self.status.value,
            "sentiment_score": self.sentiment_score,
            "valid_from": self.valid_from.isoformat(),
            "valid_until": self.valid_until.isoformat() if self.valid_until else None,
            "supersedes": self.supersedes,
            "superseded_by": self.superseded_by,
            "corroboration_count": self.corroboration_count,
            "tags": self.tags,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "MemoryNode":
        """Deserialize from dictionary."""
        return cls(
            id=data["id"],
            content=data["content"],
            memory_type=MemoryType(data["memory_type"]),
            confidence=data.get("confidence", 0.85),
            created_at=datetime.fromisoformat(data["created_at"]),
            last_accessed=datetime.fromisoformat(data["last_accessed"]),
            access_count=data.get("access_count", 0),
            source=MemorySource.from_dict(data.get("source", {})),
            status=MemoryStatus(data.get("status", "active")),
            sentiment_score=data.get("sentiment_score", 0.0),
            valid_from=datetime.fromisoformat(data["valid_from"]),
            valid_until=(
                datetime.fromisoformat(data["valid_until"]) if data.get("valid_until") else None
            ),
            supersedes=data.get("supersedes"),
            superseded_by=data.get("superseded_by"),
            corroboration_count=data.get("corroboration_count", 0),
            tags=data.get("tags", []),
        )

    def __repr__(self) -> str:
        return (
            f"MemoryNode(id={self.id[:8]}..., type={self.memory_type.value}, "
            f"confidence={self.confidence:.2f}, content={self.content[:50]!r})"
        )


@dataclass
class RelationshipEdge:
    """An edge in the knowledge graph connecting two memory nodes."""
    source_id: str
    target_id: str
    relation_type: str  # related_to, overwritten_by, corroborates, caused_by
    confidence: float = 0.8
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    valid_from: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    valid_until: Optional[datetime] = None
    metadata: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "source_id": self.source_id,
            "target_id": self.target_id,
            "relation_type": self.relation_type,
            "confidence": self.confidence,
            "created_at": self.created_at.isoformat(),
            "valid_from": self.valid_from.isoformat(),
            "valid_until": self.valid_until.isoformat() if self.valid_until else None,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "RelationshipEdge":
        return cls(
            source_id=data["source_id"],
            target_id=data["target_id"],
            relation_type=data["relation_type"],
            confidence=data.get("confidence", 0.8),
            created_at=datetime.fromisoformat(data["created_at"]),
            valid_from=datetime.fromisoformat(data["valid_from"]),
            valid_until=(
                datetime.fromisoformat(data["valid_until"]) if data.get("valid_until") else None
            ),
            metadata=data.get("metadata", {}),
        )
