"""
Hybrid session management with Redis caching and PostgreSQL persistence.
"""
import json
import logging
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any
import uuid

from redis.asyncio import Redis
from ..security.database import DatabaseManager
from .providers.ory_provider import ORYAuthProvider
from .models.session import SessionData, DeviceInfo, LocationInfo, SessionListItem

logger = logging.getLogger(__name__)


class SessionManager:
    """
    Hybrid session manager with Redis caching and PostgreSQL persistence.

    - Redis: Fast session validation (<1ms)
    - PostgreSQL: Persistent storage for 7-year audit trail
    - Kratos: Authoritative session management
    """

    REDIS_PREFIX = "session:"
    REDIS_TTL = 86400  # 24 hours
    SESSION_LIFETIME = 86400  # 24 hours
    REFRESH_THRESHOLD = 3600  # Refresh within 1 hour of expiration

    def __init__(
        self,
        redis: Redis,
        db: DatabaseManager,
        kratos_client: ORYAuthProvider
    ):
        """
        Initialize session manager.

        Args:
            redis: Redis client for caching
            db: Database manager for persistence
            kratos_client: ORY Kratos provider
        """
        self.redis = redis
        self.db = db
        self.kratos = kratos_client

    def _get_cache_key(self, session_id: str) -> str:
        """Get Redis cache key for session."""
        return f"{self.REDIS_PREFIX}{session_id}"

    async def create_session(
        self,
        identity_id: str,
        kratos_session_id: str,
        device_info: DeviceInfo,
        ip_address: str,
        location: Optional[LocationInfo] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> SessionData:
        """
        Create new session in Kratos and cache in Redis.
        Store metadata in PostgreSQL.

        Args:
            identity_id: ORY Kratos identity ID
            kratos_session_id: Kratos session ID
            device_info: Device information
            ip_address: Client IP address
            location: Geographic location
            metadata: Additional metadata

        Returns:
            SessionData
        """
        session_id = str(uuid.uuid4())
        now = datetime.utcnow()
        expires_at = now + timedelta(seconds=self.SESSION_LIFETIME)

        session = SessionData(
            session_id=session_id,
            identity_id=identity_id,
            kratos_session_id=kratos_session_id,
            ip_address=ip_address,
            device_info=device_info,
            location=location,
            created_at=now,
            last_activity_at=now,
            expires_at=expires_at,
            metadata=metadata or {}
        )

        try:
            # Store in Redis for fast access
            await self._cache_session(session)

            # Store metadata in PostgreSQL for audit trail
            await self._persist_session_metadata(session)

            logger.info(f"Created session {session_id} for identity {identity_id}")
            return session

        except Exception as e:
            logger.error(f"Failed to create session: {e}")
            raise

    async def get_session(self, session_token: str) -> Optional[SessionData]:
        """
        Get session from Redis (fast path) or Kratos (slow path).

        Args:
            session_token: Session token or session ID

        Returns:
            SessionData if valid
        """
        try:
            # Try cache first (fast path)
            cache_key = self._get_cache_key(session_token)
            cached_data = await self.redis.get(cache_key)

            if cached_data:
                session = SessionData.from_dict(json.loads(cached_data))

                # Verify session is still valid
                if session.is_valid():
                    # Update last activity
                    session.last_activity_at = datetime.utcnow()
                    await self._cache_session(session)
                    logger.debug(f"Session {session_token} retrieved from cache")
                    return session
                else:
                    # Remove invalid session from cache
                    await self.redis.delete(cache_key)
                    logger.debug(f"Removed invalid session {session_token} from cache")

            # Cache miss or invalid - check Kratos (slow path)
            user_context = await self.kratos.get_session(session_token)

            if not user_context:
                return None

            # Get session metadata from PostgreSQL
            session = await self._get_session_from_db(user_context.session_id)

            if session:
                # Refresh cache
                await self._cache_session(session)
