"""SQLAlchemy models for the memory system — standalone, no external dependencies.

10 memory-specific models extracted from the Personal Genius codebase.
All models use the SDK's own Base (from arandu.db).

user_id is Integer to match the production schema. The SDK does not define
a FK to a users table — consumers manage their own user model.
"""

import uuid
from datetime import datetime
from typing import Optional

from pgvector.sqlalchemy import Vector
from sqlalchemy import (
    Boolean,
    DateTime,
    Float,
    ForeignKey,
    Index,
    Integer,
    String,
    Text,
    UniqueConstraint,
    func,
)
from sqlalchemy.dialects.postgresql import JSONB, TSVECTOR, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship

from arandu.db import Base


class MemoryEvent(Base):
    """Immutable event log — stores all user messages with embeddings."""

    __tablename__ = "memory_events"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    text: Mapped[str] = mapped_column(Text, nullable=False)
    source: Mapped[str] = mapped_column(String, nullable=False, default="api")
    importance: Mapped[float] = mapped_column(default=0.5)
    embedding: Mapped[list | None] = mapped_column(JSONB, nullable=True)
    embedding_vec = mapped_column(Vector(1536), nullable=True)
    trace_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    emotion_primary: Mapped[str | None] = mapped_column(String(32), nullable=True)
    emotion_intensity: Mapped[float | None] = mapped_column(Float, nullable=True)
    energy_level: Mapped[str | None] = mapped_column(String(16), nullable=True)

    facts: Mapped[list["MemoryFact"]] = relationship(back_populates="source_event")


class MemoryCluster(Base):
    """Semantic cluster grouping related facts for richer context."""

    __tablename__ = "memory_clusters"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    label: Mapped[str] = mapped_column(String(128), nullable=False)
    summary_text: Mapped[str | None] = mapped_column(Text, nullable=True)
    cluster_type: Mapped[str] = mapped_column(String(32), nullable=False, default="auto")
    fact_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    importance: Mapped[float] = mapped_column(Float, default=0.5)
    embedding_vec = mapped_column(Vector(1536), nullable=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    last_updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    facts: Mapped[list["MemoryFact"]] = relationship("MemoryFact", back_populates="cluster")


class MemoryFact(Base):
    """Versioned fact ledger — stores structured facts with validity windows."""

    __tablename__ = "memory_facts"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)

    # Entity identification
    entity_type: Mapped[str] = mapped_column(String, nullable=False)
    entity_key: Mapped[str] = mapped_column(String, nullable=False)
    entity_name: Mapped[str | None] = mapped_column(String, nullable=True)

    # Attribute
    attribute_key: Mapped[str | None] = mapped_column(String, nullable=True)
    value_json: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    value_text: Mapped[str | None] = mapped_column(String, nullable=True)

    # V2: fact as natural language
    fact_text: Mapped[str] = mapped_column(Text, nullable=False, server_default="")
    category: Mapped[str | None] = mapped_column(String(50), nullable=True)

    # Scores
    confidence: Mapped[float] = mapped_column(default=0.8)
    importance: Mapped[float] = mapped_column(default=0.5)

    # Privacy
    is_sensitive: Mapped[bool] = mapped_column(Boolean, default=False)
    needs_confirmation: Mapped[bool] = mapped_column(Boolean, default=False)

    # Versioning
    valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    valid_to: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    ttl_days: Mapped[int | None] = mapped_column(Integer, nullable=True)

    # Provenance
    source_event_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("memory_events.id"), nullable=True
    )
    supersedes_fact_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)

    # Embedding
    embedding: Mapped[list | None] = mapped_column(JSONB, nullable=True)
    embedding_vec = mapped_column(Vector(1536), nullable=True)

    # Confirmation tracking
    last_confirmed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    # Cluster
    cluster_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("memory_clusters.id", ondelete="SET NULL"), nullable=True
    )

    # Retrieval tracking
    times_retrieved: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
    last_retrieved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    user_correction_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")

    # Source context
    source_context: Mapped[str | None] = mapped_column(String(512), nullable=True)

    # Agent annotation
    agent_annotation: Mapped[str | None] = mapped_column(Text, nullable=True)

    # Full-text search
    search_vector = mapped_column(TSVECTOR, nullable=True)

    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    # Bi-temporal
    ingested_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    invalidated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    # Vitality
    vitality_score: Mapped[float | None] = mapped_column(Float, nullable=True)
    is_stale: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")

    # Relationships
    source_event: Mapped[Optional["MemoryEvent"]] = relationship(back_populates="facts")
    cluster: Mapped[Optional["MemoryCluster"]] = relationship("MemoryCluster", back_populates="facts")


class MemoryMetaObservation(Base):
    """Meta-observations derived from consolidation — patterns, insights, trends."""

    __tablename__ = "memory_meta_observations"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    observation_type: Mapped[str] = mapped_column(String(32), nullable=False)
    title: Mapped[str] = mapped_column(String(256), nullable=False)
    text: Mapped[str] = mapped_column(Text, nullable=False)
    supporting_event_ids: Mapped[list] = mapped_column(JSONB, default=lambda: [])
    supporting_fact_ids: Mapped[list] = mapped_column(JSONB, default=lambda: [])
    confidence: Mapped[float] = mapped_column(Float, default=0.7)
    importance: Mapped[float] = mapped_column(Float, default=0.5)
    first_detected_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    last_reinforced_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    times_reinforced: Mapped[int] = mapped_column(Integer, default=1)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    embedding_vec = mapped_column(Vector(1536), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())


class MemoryAttributeRegistry(Base):
    """Registry for managing attribute keys — tracks proposed vs active keys."""

    __tablename__ = "memory_attribute_registry"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    key: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
    status: Mapped[str] = mapped_column(String(20), nullable=False, default="proposed")
    value_type: Mapped[str] = mapped_column(String(20), nullable=False, default="string")
    conflict_policy: Mapped[str] = mapped_column(String(20), nullable=False, default="supersede")
    ttl_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
    first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    seen_count: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    proposed_by: Mapped[str] = mapped_column(String(20), nullable=False, default="llm")
    reason: Mapped[str | None] = mapped_column(Text, nullable=True)
    example_raw_key: Mapped[str | None] = mapped_column(String(128), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)


class MemoryEntity(Base):
    """First-class entity node in the knowledge graph."""

    __tablename__ = "memory_entities"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    canonical_key: Mapped[str] = mapped_column(String(128), nullable=False)
    display_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
    entity_type: Mapped[str] = mapped_column(String(32), nullable=False)
    summary_text: Mapped[str | None] = mapped_column(Text, nullable=True)
    embedding_vec = mapped_column(Vector(1536), nullable=True)
    first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    fact_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
    importance_score: Mapped[float | None] = mapped_column(Float, nullable=True)
    summary_refreshed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        UniqueConstraint("user_id", "canonical_key", name="uq_memory_entity_user_key"),
        Index("ix_memory_entity_user_active", "user_id", "is_active"),
    )


class MemoryEntityAlias(Base):
    """Maps alias names to canonical entity keys for entity resolution."""

    __tablename__ = "memory_entity_aliases"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    alias: Mapped[str] = mapped_column(String, nullable=False)
    canonical_entity_key: Mapped[str] = mapped_column(String, nullable=False)
    canonical_entity_type: Mapped[str] = mapped_column(String, nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        UniqueConstraint("user_id", "alias", name="uq_entity_alias_user_alias"),
        Index("ix_entity_alias_user_lookup", "user_id", "alias"),
    )


class MemoryEntityRelationship(Base):
    """Directed edge between two entities in the knowledge graph."""

    __tablename__ = "memory_entity_relationships"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    source_entity_key: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
    target_entity_key: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
    rel_type: Mapped[str] = mapped_column(String(64), nullable=False)
    strength: Mapped[float] = mapped_column(Float, default=0.8)
    evidence_fact_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True), ForeignKey("memory_facts.id", ondelete="SET NULL"), nullable=True
    )
    provenance: Mapped[str] = mapped_column(String(16), default="rule")
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    valid_to: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    invalidated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    __table_args__ = (
        UniqueConstraint(
            "user_id",
            "source_entity_key",
            "target_entity_key",
            "rel_type",
            name="uq_entity_relationship",
        ),
        Index("ix_entity_rel_source", "user_id", "source_entity_key"),
        Index("ix_entity_rel_target", "user_id", "target_entity_key"),
    )


class MemoryIntention(Base):
    """Prospective memory — future intentions with time-based or event-based triggers."""

    __tablename__ = "memory_intentions"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    trigger_type: Mapped[str] = mapped_column(String(16), nullable=False)
    trigger_condition: Mapped[str] = mapped_column(Text, nullable=False)
    intended_action: Mapped[str] = mapped_column(Text, nullable=False)
    due_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    status: Mapped[str] = mapped_column(String(16), nullable=False, default="pending")
    trigger_embedding_vec = mapped_column(Vector(1536), nullable=True)
    source_context: Mapped[str | None] = mapped_column(String(32), nullable=True)
    outcome_note: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    triggered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    fulfilled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)


class SessionObservation(Base):
    """Persistent session observations created by the LLM-driven Observer."""

    __tablename__ = "session_observations"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
    content: Mapped[str] = mapped_column(Text, nullable=False)
    topic: Mapped[str | None] = mapped_column(String(64), nullable=True)
    entities_mentioned: Mapped[list] = mapped_column(JSONB, default=list)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    referenced_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    relative_offset: Mapped[str | None] = mapped_column(String(64), nullable=True)
    source_message_ids: Mapped[list] = mapped_column(JSONB, default=list)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    merged_into_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
    emotion_label: Mapped[str | None] = mapped_column(String(32), nullable=True)
    embedding_vec = mapped_column(Vector(1536), nullable=True)

    __table_args__ = (Index("idx_session_obs_user_active", "user_id", "is_active"),)
