"""
Redis-based session storage for Guacamole tokens.
"""

import json
import os
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional

import redis
import structlog

logger = structlog.get_logger(__name__)


class SessionStorage:
    """Redis-based session storage for secure Guacamole token storage."""

    def __init__(self) -> None:
        """Initialize Redis client and verify connection."""
        self.redis_client = redis.Redis(
            host=os.getenv("REDIS_HOST", "localhost"),
            port=int(os.getenv("REDIS_PORT", "6379")),
            password=os.getenv("REDIS_PASSWORD"),
            db=int(os.getenv("REDIS_DB", "0")),
            decode_responses=True,
            socket_connect_timeout=5,
            socket_timeout=5,
            retry_on_timeout=True,
        )

        try:
            self.redis_client.ping()
            logger.info("Redis connection established successfully")
        except redis.ConnectionError as e:
            logger.error("Failed to connect to Redis", error=str(e))
            raise

    def create_session(
        self,
        user_info: Dict[str, Any],
        guac_token: str,
        expires_in_minutes: int = 60,
    ) -> str:
        """
        Create new session.

        Args:
            user_info: User information dictionary.
            guac_token: Guacamole authentication token.
            expires_in_minutes: Session lifetime in minutes.

        Returns:
            Unique session ID.
        """
        session_id = str(uuid.uuid4())
        now = datetime.now(timezone.utc)

        session_data = {
            "session_id": session_id,
            "user_info": user_info,
            "guac_token": guac_token,
            "created_at": now.isoformat(),
            "expires_at": (now + timedelta(minutes=expires_in_minutes)).isoformat(),
            "last_accessed": now.isoformat(),
        }

        try:
            ttl_seconds = expires_in_minutes * 60
            self.redis_client.setex(
                f"session:{session_id}",
                ttl_seconds,
                json.dumps(session_data),
            )

            self.redis_client.setex(
                f"user_session:{user_info['username']}",
                ttl_seconds,
                session_id,
            )

            logger.info(
                "Session created successfully",
                session_id=session_id,
                username=user_info["username"],
                expires_in_minutes=expires_in_minutes,
                redis_key=f"session:{session_id}",
                has_guac_token=bool(guac_token),
                guac_token_length=len(guac_token) if guac_token else 0,
            )

            return session_id

        except redis.RedisError as e:
            logger.error("Failed to create session", error=str(e))
            raise
    
    def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
        """
        Get session data.

        Args:
            session_id: Session ID.

        Returns:
            Session data or None if not found/expired.
        """
        try:
            session_data = self.redis_client.get(f"session:{session_id}")

            if not session_data:
                logger.debug("Session not found", session_id=session_id)
                return None

            session = json.loads(session_data)
            session["last_accessed"] = datetime.now(timezone.utc).isoformat()

            ttl = self.redis_client.ttl(f"session:{session_id}")
            if ttl > 0:
                self.redis_client.setex(
                    f"session:{session_id}",
                    ttl,
                    json.dumps(session),
                )

            logger.debug(
                "Session retrieved successfully",
                session_id=session_id,
                username=session["user_info"]["username"],
            )

            return session

        except redis.RedisError as e:
            logger.error("Failed to get session", session_id=session_id, error=str(e))
            return None
        except json.JSONDecodeError as e:
            logger.error(
                "Failed to decode session data", session_id=session_id, error=str(e)
            )
            return None
    
    def get_session_by_username(self, username: str) -> Optional[Dict[str, Any]]:
        """
        Get session by username.

        Args:
            username: Username.

        Returns:
            Session data or None.
        """
        try:
            session_id = self.redis_client.get(f"user_session:{username}")

            if not session_id:
                logger.debug("No active session for user", username=username)
                return None

            return self.get_session(session_id)

        except redis.RedisError as e:
            logger.error(
                "Failed to get session by username", username=username, error=str(e)
            )
            return None
    
    def update_session(self, session_id: str, updates: Dict[str, Any]) -> bool:
        """
        Update session data.

        Args:
            session_id: Session ID.
            updates: Updates to apply.

        Returns:
            True if update successful.
        """
        try:
            session_data = self.redis_client.get(f"session:{session_id}")

            if not session_data:
                logger.warning("Session not found for update", session_id=session_id)
                return False

            session = json.loads(session_data)
            session.update(updates)
            session["last_accessed"] = datetime.now(timezone.utc).isoformat()

            ttl = self.redis_client.ttl(f"session:{session_id}")
            if ttl > 0:
                self.redis_client.setex(
                    f"session:{session_id}",
                    ttl,
                    json.dumps(session),
                )

                logger.debug(
                    "Session updated successfully",
                    session_id=session_id,
                    updates=list(updates.keys()),
                )
                return True
            else:
                logger.warning("Session expired during update", session_id=session_id)
                return False

        except redis.RedisError as e:
            logger.error("Failed to update session", session_id=session_id, error=str(e))
            return False
        except json.JSONDecodeError as e:
            logger.error(
                "Failed to decode session data for update",
                session_id=session_id,
                error=str(e),
            )
            return False
    
    def delete_session(self, session_id: str) -> bool:
        """
        Delete session.

        Args:
            session_id: Session ID.

        Returns:
            True if deletion successful.
        """
        try:
            session_data = self.redis_client.get(f"session:{session_id}")

            if session_data:
                session = json.loads(session_data)
                username = session["user_info"]["username"]

                self.redis_client.delete(f"session:{session_id}")
                self.redis_client.delete(f"user_session:{username}")

                logger.info(
                    "Session deleted successfully",
                    session_id=session_id,
                    username=username,
                )
                return True
            else:
                logger.debug("Session not found for deletion", session_id=session_id)
                return False

        except redis.RedisError as e:
            logger.error("Failed to delete session", session_id=session_id, error=str(e))
            return False
        except json.JSONDecodeError as e:
            logger.error(
                "Failed to decode session data for deletion",
                session_id=session_id,
                error=str(e),
            )
            return False
    
    def delete_user_sessions(self, username: str) -> int:
        """
        Delete all user sessions.

        Args:
            username: Username.

        Returns:
            Number of deleted sessions.
        """
        try:
            pattern = f"user_session:{username}"
            session_keys = self.redis_client.keys(pattern)

            deleted_count = 0
            for key in session_keys:
                session_id = self.redis_client.get(key)
                if session_id and self.delete_session(session_id):
                    deleted_count += 1

            logger.info(
                "User sessions deleted", username=username, deleted_count=deleted_count
            )

            return deleted_count

        except redis.RedisError as e:
            logger.error(
                "Failed to delete user sessions", username=username, error=str(e)
            )
            return 0
    
    def cleanup_expired_sessions(self) -> int:
        """
        Cleanup expired sessions.

        Redis automatically removes keys by TTL, so this method is mainly
        for compatibility and potential logic extension.

        Returns:
            Number of cleaned sessions (always 0, as Redis handles this automatically).
        """
        logger.debug(
            "Expired sessions cleanup completed (Redis TTL handles this automatically)"
        )
        return 0
    
    def get_session_stats(self) -> Dict[str, Any]:
        """
        Get session statistics.

        Returns:
            Session statistics dictionary.
        """
        try:
            session_keys = self.redis_client.keys("session:*")
            user_keys = self.redis_client.keys("user_session:*")

            memory_usage = (
                self.redis_client.memory_usage("session:*") if session_keys else 0
            )

            return {
                "active_sessions": len(session_keys),
                "active_users": len(user_keys),
                "redis_memory_usage": memory_usage,
            }

        except redis.RedisError as e:
            logger.error("Failed to get session stats", error=str(e))
            return {
                "active_sessions": 0,
                "active_users": 0,
                "redis_memory_usage": 0,
                "error": str(e),
            }


session_storage = SessionStorage()
