"""
DataFlow Models for Journey Orchestration (REQ-INT-004).

Provides DataFlow-decorated models for production persistence of journey sessions,
conversations, and intent caches. These models enable enterprise-grade storage
with automatic schema management, multi-tenancy support, and efficient queries.

Models:
    - JourneySessionModel: Active journey session state (replaces MemoryStateBackend)
    - JourneyConversationModel: Individual conversation turns with timestamps
    - IntentCacheModel: Cached intent detection results for performance

Architecture:
    Journey Orchestration
    +-- PathwayManager
    +-- JourneyStateManager
        +-- DataFlowStateBackend
            +-- JourneySessionModel (session state)
            +-- JourneyConversationModel (conversation history)
            +-- IntentCacheModel (intent detection cache)

Usage:
    from dataflow import DataFlow
    from kaizen.journey.models import register_journey_models

    # Initialize DataFlow
    db = DataFlow("postgresql://...")

    # Register journey models
    register_journey_models(db)

    # Models are now available for CRUD operations
    await db.express.create("JourneySession", {...})

References:
    - docs/plans/03-journey/06-integration.md
    - TODO-JO-005: Integration Requirements
"""

import json
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Dict, List, Optional

if TYPE_CHECKING:
    from dataflow import DataFlow

logger = logging.getLogger(__name__)


# ============================================================================
# Model Definitions (REQ-INT-004)
# ============================================================================


@dataclass
class JourneySessionModel:
    """
    DataFlow model for journey session persistence.

    Stores complete session state including current pathway, navigation stack,
    accumulated context, and timestamps. Designed for efficient restore/update.

    Attributes:
        id: Unique session identifier (primary key)
        journey_class: Fully qualified journey class name (for restoration)
        current_pathway_id: Currently active pathway
        pathway_stack: JSON-serialized navigation stack
        accumulated_context: JSON-serialized cross-pathway context
        created_at: Session creation timestamp (ISO format)
        updated_at: Last modification timestamp (ISO format)
        expires_at: Optional TTL expiration timestamp
        tenant_id: Optional multi-tenant isolation key

    Schema (auto-generated by DataFlow):
        CREATE TABLE journey_session (
            id VARCHAR(255) PRIMARY KEY,
            journey_class VARCHAR(500),
            current_pathway_id VARCHAR(255),
            pathway_stack TEXT,  -- JSON array
            accumulated_context TEXT,  -- JSON object
            created_at VARCHAR(30),
            updated_at VARCHAR(30),
            expires_at VARCHAR(30),
            tenant_id VARCHAR(255)
        );
    """

    id: str
    journey_class: str = ""
    current_pathway_id: str = ""
    pathway_stack: str = "[]"  # JSON array
    accumulated_context: str = "{}"  # JSON object
    created_at: str = ""
    updated_at: str = ""
    expires_at: Optional[str] = None
    tenant_id: Optional[str] = None


@dataclass
class JourneyConversationModel:
    """
    DataFlow model for conversation turn storage.

    Stores individual conversation turns with rich metadata for analytics,
    replay, and audit trails. Supports efficient pagination and filtering.

    Attributes:
        id: Unique turn identifier (auto-generated UUID recommended)
        session_id: Parent session identifier (foreign key)
        turn_number: Sequential turn number within session
        role: Message role ("user", "assistant", "system")
        content: Message content
        pathway_id: Pathway that processed this turn
        timestamp: Turn creation timestamp (ISO format)
        metadata: JSON-serialized additional metadata
        tenant_id: Optional multi-tenant isolation key

    Schema (auto-generated by DataFlow):
        CREATE TABLE journey_conversation (
            id VARCHAR(255) PRIMARY KEY,
            session_id VARCHAR(255),
            turn_number INTEGER,
            role VARCHAR(50),
            content TEXT,
            pathway_id VARCHAR(255),
            timestamp VARCHAR(30),
            metadata TEXT,  -- JSON object
            tenant_id VARCHAR(255)
        );

    Indexes (recommended):
        CREATE INDEX idx_conv_session ON journey_conversation(session_id);
        CREATE INDEX idx_conv_timestamp ON journey_conversation(timestamp);
    """

    id: str
    session_id: str
    turn_number: int = 0
    role: str = "user"
    content: str = ""
    pathway_id: str = ""
    timestamp: str = ""
    metadata: str = "{}"  # JSON object
    tenant_id: Optional[str] = None


@dataclass
class IntentCacheModel:
    """
    DataFlow model for intent detection cache.

    Caches intent classification results to avoid redundant LLM calls.
    Supports TTL-based expiration and session-scoped invalidation.

    Attributes:
        id: Cache key (typically hash of input + model)
        session_id: Optional session scope for cache isolation
        input_hash: Hash of the input message
        intent: Detected intent name
        confidence: Classification confidence (0.0-1.0)
        model: Model used for classification
        created_at: Cache entry timestamp
        expires_at: TTL expiration timestamp
        metadata: JSON-serialized additional data

    Schema (auto-generated by DataFlow):
        CREATE TABLE intent_cache (
            id VARCHAR(255) PRIMARY KEY,
            session_id VARCHAR(255),
            input_hash VARCHAR(64),
            intent VARCHAR(255),
            confidence FLOAT,
            model VARCHAR(255),
            created_at VARCHAR(30),
            expires_at VARCHAR(30),
            metadata TEXT  -- JSON object
        );

    Cache Strategy:
        - Key: md5(input_message + model_name)
        - TTL: Configurable (default: 300 seconds)
        - Scope: Session-level or global
    """

    id: str
    session_id: Optional[str] = None
    input_hash: str = ""
    intent: str = ""
    confidence: float = 0.0
    model: str = ""
    created_at: str = ""
    expires_at: str = ""
    metadata: str = "{}"  # JSON object


# ============================================================================
# Model Registration (REQ-INT-004)
# ============================================================================


def register_journey_models(db: "DataFlow") -> None:
    """
    Register journey models with DataFlow instance.

    Decorates and registers all journey models with the provided DataFlow
    instance, enabling automatic schema creation and CRUD operations.

    Args:
        db: DataFlow instance to register models with

    Example:
        from dataflow import DataFlow
        from kaizen.journey.models import register_journey_models

        db = DataFlow("postgresql://localhost/kaizen")
        register_journey_models(db)

        # Now use models via DataFlow Express API
        await db.express.create("JourneySession", {
            "id": "session-123",
            "journey_class": "myapp.journeys.BookingJourney",
            "current_pathway_id": "intake"
        })

    Note:
        This function should be called once during application startup,
        before any journey operations are performed.
    """

    # Register JourneySession model
    @db.model
    class JourneySession:
        id: str
        journey_class: str
        current_pathway_id: str
        pathway_stack: str
        accumulated_context: str
        created_at: str
        updated_at: str
        expires_at: Optional[str] = None
        tenant_id: Optional[str] = None

    # Register JourneyConversation model
    @db.model
    class JourneyConversation:
        id: str
        session_id: str
        turn_number: int
        role: str
        content: str
        pathway_id: str
        timestamp: str
        metadata: str
        tenant_id: Optional[str] = None

    # Register IntentCache model
    @db.model
    class IntentCache:
        id: str
        session_id: Optional[str] = None
        input_hash: str
        intent: str
        confidence: float
        model: str
        created_at: str
        expires_at: str
        metadata: str

    logger.info("Registered journey models with DataFlow")


# ============================================================================
# DataFlow State Backend Enhancement (REQ-INT-004)
# ============================================================================


class EnhancedDataFlowStateBackend:
    """
    Enhanced DataFlow backend with conversation and intent cache support.

    Extends the basic DataFlowStateBackend with:
    - Conversation history persistence (separate table)
    - Intent cache management with TTL
    - Efficient pagination for large histories
    - Multi-tenant isolation

    Attributes:
        db: DataFlow instance
        session_model: Model name for sessions (default: "JourneySession")
        conversation_model: Model name for conversations (default: "JourneyConversation")
        intent_cache_model: Model name for intent cache (default: "IntentCache")
        tenant_id: Optional tenant identifier for isolation

    Example:
        from dataflow import DataFlow
        from kaizen.journey.models import EnhancedDataFlowStateBackend, register_journey_models

        db = DataFlow("postgresql://...")
        register_journey_models(db)

        backend = EnhancedDataFlowStateBackend(db, tenant_id="tenant-001")

        # Save session with conversation
        await backend.save_with_conversation(session, conversation_turns)

        # Load paginated conversation history
        turns = await backend.load_conversation(session_id, limit=50, offset=0)
    """

    def __init__(
        self,
        db: "DataFlow",
        session_model: str = "JourneySession",
        conversation_model: str = "JourneyConversation",
        intent_cache_model: str = "IntentCache",
        tenant_id: Optional[str] = None,
    ):
        """
        Initialize enhanced backend.

        Args:
            db: DataFlow instance
            session_model: Model name for sessions
            conversation_model: Model name for conversations
            intent_cache_model: Model name for intent cache
            tenant_id: Optional tenant ID for multi-tenancy
        """
        self.db = db
        self.session_model = session_model
        self.conversation_model = conversation_model
        self.intent_cache_model = intent_cache_model
        self.tenant_id = tenant_id

    # ========================================================================
    # Session Operations
    # ========================================================================

    async def save_session(
        self,
        session_id: str,
        data: Dict[str, Any],
    ) -> None:
        """
        Save session data.

        Args:
            session_id: Session identifier
            data: Session state data
        """
        now = datetime.now(timezone.utc).isoformat()

        serialized = {
            "id": session_id,
            "journey_class": data.get("journey_class", ""),
            "current_pathway_id": data.get("current_pathway_id", ""),
            "pathway_stack": json.dumps(data.get("pathway_stack", [])),
            "accumulated_context": json.dumps(data.get("accumulated_context", {})),
            "created_at": data.get("created_at", now),
            "updated_at": now,
            "expires_at": data.get("expires_at"),
            "tenant_id": self.tenant_id,
        }

        # Check if exists
        existing = await self.db.express.read(self.session_model, session_id)

        if existing:
            update_fields = {k: v for k, v in serialized.items() if k != "id"}
            await self.db.express.update(self.session_model, session_id, update_fields)
        else:
            await self.db.express.create(self.session_model, serialized)

    async def load_session(self, session_id: str) -> Optional[Dict[str, Any]]:
        """
        Load session data.

        Args:
            session_id: Session identifier

        Returns:
            Session data dict or None if not found
        """
        record = await self.db.express.read(self.session_model, session_id)

        if not record:
            return None

        # Check tenant isolation
        if self.tenant_id and record.get("tenant_id") != self.tenant_id:
            return None

        return {
            "session_id": record.get("id"),
            "journey_class": record.get("journey_class", ""),
            "current_pathway_id": record.get("current_pathway_id", ""),
            "pathway_stack": json.loads(record.get("pathway_stack", "[]")),
            "accumulated_context": json.loads(record.get("accumulated_context", "{}")),
            "created_at": record.get("created_at"),
            "updated_at": record.get("updated_at"),
            "expires_at": record.get("expires_at"),
        }

    async def delete_session(self, session_id: str) -> None:
        """
        Delete session and associated data.

        Args:
            session_id: Session identifier
        """
        # Delete conversation history
        await self._delete_conversation(session_id)

        # Delete intent cache
        await self._delete_intent_cache_for_session(session_id)

        # Delete session
        await self.db.express.delete(self.session_model, session_id)

    # ========================================================================
    # Conversation Operations
    # ========================================================================

    async def save_conversation_turn(
        self,
        session_id: str,
        turn_id: str,
        turn_number: int,
        role: str,
        content: str,
        pathway_id: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Save a single conversation turn.

        Args:
            session_id: Parent session ID
            turn_id: Unique turn identifier
            turn_number: Sequential turn number
            role: Message role (user/assistant/system)
            content: Message content
            pathway_id: Pathway that handled this turn
            metadata: Optional additional metadata
        """
        await self.db.express.create(
            self.conversation_model,
            {
                "id": turn_id,
                "session_id": session_id,
                "turn_number": turn_number,
                "role": role,
                "content": content,
                "pathway_id": pathway_id,
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "metadata": json.dumps(metadata or {}),
                "tenant_id": self.tenant_id,
            },
        )

    async def load_conversation(
        self,
        session_id: str,
        limit: int = 100,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """
        Load paginated conversation history.

        Args:
            session_id: Session identifier
            limit: Maximum turns to return
            offset: Number of turns to skip

        Returns:
            List of conversation turn dicts, ordered by turn_number
        """
        filter_query = {"session_id": session_id}
        if self.tenant_id:
            filter_query["tenant_id"] = self.tenant_id

        records = await self.db.express.list(
            self.conversation_model,
            filter=filter_query,
            limit=limit,
            offset=offset,
        )

        turns = []
        for record in records:
            turns.append(
                {
                    "turn_id": record.get("id"),
                    "turn_number": record.get("turn_number", 0),
                    "role": record.get("role", ""),
                    "content": record.get("content", ""),
                    "pathway_id": record.get("pathway_id", ""),
                    "timestamp": record.get("timestamp", ""),
                    "metadata": json.loads(record.get("metadata", "{}")),
                }
            )

        # Sort by turn number
        turns.sort(key=lambda t: t.get("turn_number", 0))

        return turns

    async def _delete_conversation(self, session_id: str) -> None:
        """Delete all conversation turns for a session."""
        filter_query = {"session_id": session_id}
        if self.tenant_id:
            filter_query["tenant_id"] = self.tenant_id

        # List all turns
        records = await self.db.express.list(
            self.conversation_model,
            filter=filter_query,
            limit=10000,
        )

        # Delete each turn
        for record in records:
            await self.db.express.delete(self.conversation_model, record.get("id"))

    # ========================================================================
    # Intent Cache Operations
    # ========================================================================

    async def get_cached_intent(
        self,
        input_hash: str,
        session_id: Optional[str] = None,
    ) -> Optional[Dict[str, Any]]:
        """
        Get cached intent result.

        Args:
            input_hash: Hash of the input message
            session_id: Optional session scope

        Returns:
            Cached intent data or None if not found/expired
        """
        cache_id = f"{session_id or 'global'}:{input_hash}"

        record = await self.db.express.read(self.intent_cache_model, cache_id)

        if not record:
            return None

        # Check expiration
        expires_at = record.get("expires_at", "")
        if expires_at:
            try:
                expiry = datetime.fromisoformat(expires_at)
                if datetime.now(timezone.utc) > expiry:
                    # Expired - delete and return None
                    await self.db.express.delete(self.intent_cache_model, cache_id)
                    return None
            except ValueError as e:
                # Log malformed timestamp - may indicate data corruption
                logger.warning(
                    f"Invalid expires_at timestamp '{expires_at}' for cache {cache_id}: {e}"
                )

        return {
            "intent": record.get("intent", ""),
            "confidence": record.get("confidence", 0.0),
            "model": record.get("model", ""),
            "created_at": record.get("created_at", ""),
            "metadata": json.loads(record.get("metadata", "{}")),
        }

    async def cache_intent(
        self,
        input_hash: str,
        intent: str,
        confidence: float,
        model: str,
        ttl_seconds: int = 300,
        session_id: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Cache intent detection result.

        Args:
            input_hash: Hash of the input message
            intent: Detected intent name
            confidence: Classification confidence
            model: Model used for classification
            ttl_seconds: Cache TTL in seconds (default: 300)
            session_id: Optional session scope
            metadata: Optional additional metadata
        """
        from datetime import timedelta

        cache_id = f"{session_id or 'global'}:{input_hash}"
        now = datetime.now(timezone.utc)
        expires_at = now + timedelta(seconds=ttl_seconds)

        await self.db.express.create(
            self.intent_cache_model,
            {
                "id": cache_id,
                "session_id": session_id,
                "input_hash": input_hash,
                "intent": intent,
                "confidence": confidence,
                "model": model,
                "created_at": now.isoformat(),
                "expires_at": expires_at.isoformat(),
                "metadata": json.dumps(metadata or {}),
            },
        )

    async def _delete_intent_cache_for_session(self, session_id: str) -> None:
        """Delete all intent cache entries for a session."""
        filter_query = {"session_id": session_id}

        records = await self.db.express.list(
            self.intent_cache_model,
            filter=filter_query,
            limit=10000,
        )

        for record in records:
            await self.db.express.delete(self.intent_cache_model, record.get("id"))


__all__ = [
    # Model dataclasses
    "JourneySessionModel",
    "JourneyConversationModel",
    "IntentCacheModel",
    # Registration
    "register_journey_models",
    # Enhanced backend
    "EnhancedDataFlowStateBackend",
]
