"""
Demo Session Manager
Handles creation, extension, and destruction of demo sessions
"""

from sqlalchemy.ext.asyncio import AsyncSession
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any
import uuid
import secrets
import structlog
from sqlalchemy import select

from app.models import DemoSession, DemoSessionStatus, CloningStatus
from app.core.redis_wrapper import DemoRedisWrapper
from app.core import settings
from app.services.clone_orchestrator import CloneOrchestrator
from app.services.cleanup_service import DemoCleanupService
from app.repositories.demo_session_repository import DemoSessionRepository

logger = structlog.get_logger()


class DemoSessionManager:
    """Manages demo session lifecycle"""

    def __init__(self, db: AsyncSession, redis: DemoRedisWrapper):
        self.db = db
        self.redis = redis
        self.repository = DemoSessionRepository(db)
        self.orchestrator = CloneOrchestrator(redis_manager=redis)  # Pass Redis for real-time progress updates

    async def create_session(
        self,
        demo_account_type: str,
        subscription_tier: Optional[str] = None,
        user_id: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None
    ) -> DemoSession:
        """
        Create a new demo session

        Args:
            demo_account_type: 'professional' or 'enterprise'
            subscription_tier: Force specific subscription tier (professional/enterprise)
            user_id: Optional user ID if authenticated
            ip_address: Client IP address
            user_agent: Client user agent

        Returns:
            Created demo session
        """
        logger.info("Creating demo session",
                   demo_account_type=demo_account_type,
                   subscription_tier=subscription_tier)

        # Generate unique session ID
        session_id = f"demo_{secrets.token_urlsafe(16)}"

        # Generate virtual tenant ID
        virtual_tenant_id = uuid.uuid4()

        # Get base demo tenant ID from config
        demo_config = settings.DEMO_ACCOUNTS.get(demo_account_type)
        if not demo_config:
            raise ValueError(f"Invalid demo account type: {demo_account_type}")

        # Override subscription tier if specified
        effective_subscription_tier = subscription_tier or demo_config.get("subscription_tier")

        # Get base tenant ID for cloning
        base_tenant_id_str = demo_config.get("base_tenant_id")
        if not base_tenant_id_str:
            raise ValueError(f"Base tenant ID not configured for demo account type: {demo_account_type}")

        base_tenant_id = uuid.UUID(base_tenant_id_str)

        # Handle enterprise chain setup
        child_tenant_ids = []
        if demo_account_type == 'enterprise':
            # Generate child tenant IDs for enterprise demos
            child_configs = demo_config.get('children', [])
            child_tenant_ids = [uuid.uuid4() for _ in child_configs]

        # Create session record using repository
        session_data = {
            "session_id": session_id,
            "user_id": uuid.UUID(user_id) if user_id else None,
            "ip_address": ip_address,
            "user_agent": user_agent,
            "base_demo_tenant_id": base_tenant_id,
            "virtual_tenant_id": virtual_tenant_id,
            "demo_account_type": demo_account_type,
            "status": DemoSessionStatus.PENDING,  # Start as pending until cloning completes
            "created_at": datetime.now(timezone.utc),
            "expires_at": datetime.now(timezone.utc) + timedelta(
                minutes=settings.DEMO_SESSION_DURATION_MINUTES
            ),
            "last_activity_at": datetime.now(timezone.utc),
            "data_cloned": False,
            "redis_populated": False,
            "session_metadata": {
                "demo_config": demo_config,
                "subscription_tier": effective_subscription_tier,
                "extension_count": 0,
                "is_enterprise": demo_account_type == 'enterprise',
                "child_tenant_ids": [str(tid) for tid in child_tenant_ids] if child_tenant_ids else [],
                "child_configs": demo_config.get('children', []) if demo_account_type == 'enterprise' else []
            }
        }

        session = await self.repository.create(session_data)

        # Store session metadata in Redis
        await self._store_session_metadata(session)

        logger.info(
            "Demo session created",
            session_id=session_id,
            virtual_tenant_id=str(virtual_tenant_id),
            demo_account_type=demo_account_type,
            is_enterprise=demo_account_type == 'enterprise',
            child_tenant_count=len(child_tenant_ids),
            expires_at=session.expires_at.isoformat()
        )

        return session

    async def get_session(self, session_id: str) -> Optional[DemoSession]:
        """Get session by session_id"""
        return await self.repository.get_by_session_id(session_id)

    async def get_session_by_virtual_tenant(self, virtual_tenant_id: str) -> Optional[DemoSession]:
        """Get session by virtual tenant ID"""
        return await self.repository.get_by_virtual_tenant_id(uuid.UUID(virtual_tenant_id))

    async def extend_session(self, session_id: str) -> DemoSession:
        """
        Extend session expiration time

        Args:
            session_id: Session ID to extend

        Returns:
            Updated session

        Raises:
            ValueError: If session cannot be extended
        """
        session = await self.get_session(session_id)

        if not session:
            raise ValueError(f"Session not found: {session_id}")

        if session.status != DemoSessionStatus.ACTIVE:
            raise ValueError(f"Cannot extend {session.status.value} session")

        # Check extension limit
        extension_count = session.session_metadata.get("extension_count", 0)
        if extension_count >= settings.DEMO_SESSION_MAX_EXTENSIONS:
            raise ValueError(f"Maximum extensions ({settings.DEMO_SESSION_MAX_EXTENSIONS}) reached")

        # Extend expiration
        new_expires_at = datetime.now(timezone.utc) + timedelta(
            minutes=settings.DEMO_SESSION_DURATION_MINUTES
        )

        session.expires_at = new_expires_at
        session.last_activity_at = datetime.now(timezone.utc)
        session.session_metadata["extension_count"] = extension_count + 1

        session = await self.repository.update(session)

        # Extend Redis TTL
        await self.redis.extend_session_ttl(
            session_id,
            settings.REDIS_SESSION_TTL
        )

        logger.info(
            "Session extended",
            session_id=session_id,
            new_expires_at=new_expires_at.isoformat(),
            extension_count=extension_count + 1
        )

        return session

    async def update_activity(self, session_id: str):
        """Update last activity timestamp"""
        await self.repository.update_activity(session_id)

    async def mark_data_cloned(self, session_id: str):
        """Mark session as having data cloned"""
        await self.repository.mark_data_cloned(session_id)

    async def mark_redis_populated(self, session_id: str):
        """Mark session as having Redis data populated"""
        await self.repository.mark_redis_populated(session_id)

    async def destroy_session(self, session_id: str):
        """
        Destroy a demo session and cleanup resources
        This triggers parallel deletion across all services.
        """
        session = await self.get_session(session_id)

        if not session:
            logger.warning("Session not found for destruction", session_id=session_id)
            return

        try:
            # Update status to DESTROYING
            await self.repository.update_fields(
                session_id,
                status=DemoSessionStatus.DESTROYING
            )

            # Trigger cleanup across all services
            cleanup_service = DemoCleanupService(self.db, self.redis)
            result = await cleanup_service.cleanup_session(session)

            if result["success"]:
                # Update status to DESTROYED
                await self.repository.update_fields(
                    session_id,
                    status=DemoSessionStatus.DESTROYED,
                    destroyed_at=datetime.now(timezone.utc)
                )
            else:
                # Update status to FAILED with error details
                await self.repository.update_fields(
                    session_id,
                    status=DemoSessionStatus.FAILED,
                    error_details=result["errors"]
                )

            # Delete Redis data
            await self.redis.delete_session_data(session_id)

            logger.info(
                "Session destroyed",
                session_id=session_id,
                virtual_tenant_id=str(session.virtual_tenant_id),
                total_records_deleted=result.get("total_deleted", 0),
                duration_ms=result.get("duration_ms", 0)
            )

        except Exception as e:
            logger.error(
                "Failed to destroy session",
                session_id=session_id,
                error=str(e),
                exc_info=True
            )
            # Update status to FAILED with error details
            await self.repository.update_fields(
                session_id,
                status=DemoSessionStatus.FAILED,
                error_details=[f"Cleanup failed: {str(e)}"]
            )
            raise

    async def _check_database_disk_space(self):
        """Check if database has sufficient disk space for demo operations"""
        try:
            # Execute a simple query to check database health and disk space
            # This is a basic check - in production you might want more comprehensive monitoring
            from sqlalchemy import text
            
            # Check if we can execute a simple query (indicates basic database health)
            result = await self.db.execute(text("SELECT 1"))
            # Get the scalar result properly
            scalar_result = result.scalar_one_or_none()
            
            # For more comprehensive checking, you could add:
            # 1. Check table sizes
            # 2. Check available disk space via system queries (if permissions allow)
            # 3. Check for long-running transactions that might block operations
            
            logger.debug("Database health check passed", result=scalar_result)
            
        except Exception as e:
            logger.error("Database health check failed", error=str(e), exc_info=True)
            raise RuntimeError(f"Database health check failed: {str(e)}")

    async def _store_session_metadata(self, session: DemoSession):
        """Store session metadata in Redis"""
        await self.redis.set_session_data(
            session.session_id,
            "metadata",
            {
                "session_id": session.session_id,
                "virtual_tenant_id": str(session.virtual_tenant_id),
                "demo_account_type": session.demo_account_type,
                "expires_at": session.expires_at.isoformat(),
                "created_at": session.created_at.isoformat()
            },
            ttl=settings.REDIS_SESSION_TTL
        )

    async def get_active_sessions_count(self) -> int:
        """Get count of active sessions"""
        return await self.repository.get_active_sessions_count()

    async def get_session_stats(self) -> Dict[str, Any]:
        """Get session statistics"""
        return await self.repository.get_session_stats()

    async def trigger_orchestrated_cloning(
        self,
        session: DemoSession,
        base_tenant_id: str
    ) -> Dict[str, Any]:
        """
        Trigger orchestrated cloning across all services

        Args:
            session: Demo session
            base_tenant_id: Template tenant ID to clone from

        Returns:
            Orchestration result
        """
        logger.info(
            "Triggering orchestrated cloning",
            session_id=session.session_id,
            virtual_tenant_id=str(session.virtual_tenant_id)
        )

        # Check database disk space before starting cloning
        try:
            await self._check_database_disk_space()
        except Exception as e:
            logger.error(
                "Database disk space check failed",
                session_id=session.session_id,
                error=str(e)
            )
            # Mark session as failed due to infrastructure issue
            session.status = DemoSessionStatus.FAILED
            session.cloning_completed_at = datetime.now(timezone.utc)
            session.total_records_cloned = 0
            session.cloning_progress = {
                "error": "Database disk space issue detected",
                "details": str(e)
            }
            await self.repository.update(session)
            await self._cache_session_status(session)
            return {
                "overall_status": "failed",
                "services": {},
                "total_records": 0,
                "failed_services": ["database"],
                "error": "Database disk space issue"
            }

        # Mark cloning as started and update both database and Redis cache
        session.cloning_started_at = datetime.now(timezone.utc)
        await self.repository.update(session)

        # Update Redis cache to reflect that cloning has started
        await self._cache_session_status(session)

        # Run orchestration
        result = await self.orchestrator.clone_all_services(
            base_tenant_id=base_tenant_id,
            virtual_tenant_id=str(session.virtual_tenant_id),
            demo_account_type=session.demo_account_type,
            session_id=session.session_id,
            session_metadata=session.session_metadata
        )

        # Update session with results
        await self._update_session_from_clone_result(session, result)

        return result



    async def _update_session_from_clone_result(
        self,
        session: DemoSession,
        clone_result: Dict[str, Any]
    ):
        """Update session with cloning results"""

        # Map overall status to session status
        overall_status = clone_result.get("overall_status")
        if overall_status in ["ready", "completed"]:
            session.status = DemoSessionStatus.READY
        elif overall_status == "failed":
            session.status = DemoSessionStatus.FAILED
        elif overall_status == "partial":
            session.status = DemoSessionStatus.PARTIAL

        # Update cloning metadata
        session.cloning_completed_at = datetime.now(timezone.utc)
        # The clone result might use 'total_records' or 'total_records_cloned'
        session.total_records_cloned = clone_result.get("total_records_cloned",
                                                      clone_result.get("total_records", 0))
        session.cloning_progress = clone_result.get("services", {})

        # Mark legacy flags for backward compatibility
        if overall_status in ["ready", "completed", "partial"]:
            session.data_cloned = True
            session.redis_populated = True

        await self.repository.update(session)

        # Cache status in Redis for fast polling
        await self._cache_session_status(session)

        logger.info(
            "Session updated with clone results",
            session_id=session.session_id,
            status=session.status.value,
            total_records=session.total_records_cloned
        )

    async def _cache_session_status(self, session: DemoSession):
        """Cache session status in Redis for fast status checks"""
        status_key = f"session:{session.session_id}:status"

        # Calculate estimated remaining time based on demo tier
        estimated_remaining_seconds = None
        if session.cloning_started_at and not session.cloning_completed_at:
            elapsed = (datetime.now(timezone.utc) - session.cloning_started_at).total_seconds()
            avg_duration = 5
            estimated_remaining_seconds = max(0, int(avg_duration - elapsed))

        status_data = {
            "session_id": session.session_id,
            "status": session.status.value,
            "progress": session.cloning_progress,
            "total_records_cloned": session.total_records_cloned,
            "cloning_started_at": session.cloning_started_at.isoformat() if session.cloning_started_at else None,
            "cloning_completed_at": session.cloning_completed_at.isoformat() if session.cloning_completed_at else None,
            "expires_at": session.expires_at.isoformat(),
            "estimated_remaining_seconds": estimated_remaining_seconds,
            "demo_account_type": session.demo_account_type
        }

        import json as json_module
        client = await self.redis.get_client()
        await client.setex(
            status_key,
            7200,  # Cache for 2 hours
            json_module.dumps(status_data)  # Convert to JSON string
        )

    async def get_session_status(self, session_id: str) -> Dict[str, Any]:
        """
        Get current session status with cloning progress

        Args:
            session_id: Session ID

        Returns:
            Status information including per-service progress
        """
        # Try Redis cache first
        status_key = f"session:{session_id}:status"
        client = await self.redis.get_client()
        cached = await client.get(status_key)

        if cached:
            import json
            return json.loads(cached)

        # Fall back to database
        session = await self.get_session(session_id)
        if not session:
            return None

        await self._cache_session_status(session)

        # Calculate estimated remaining time for database fallback
        estimated_remaining_seconds = None
        if session.cloning_started_at and not session.cloning_completed_at:
            elapsed = (datetime.now(timezone.utc) - session.cloning_started_at).total_seconds()
            avg_duration = 5
            estimated_remaining_seconds = max(0, int(avg_duration - elapsed))

        return {
            "session_id": session.session_id,
            "status": session.status.value,
            "progress": session.cloning_progress,
            "total_records_cloned": session.total_records_cloned,
            "cloning_started_at": session.cloning_started_at.isoformat() if session.cloning_started_at else None,
            "cloning_completed_at": session.cloning_completed_at.isoformat() if session.cloning_completed_at else None,
            "expires_at": session.expires_at.isoformat(),
            "estimated_remaining_seconds": estimated_remaining_seconds,
            "demo_account_type": session.demo_account_type
        }

    async def retry_failed_cloning(
        self,
        session_id: str,
        services: Optional[list] = None
    ) -> Dict[str, Any]:
        """
        Retry failed cloning operations

        Args:
            session_id: Session ID
            services: Specific services to retry (defaults to all failed)

        Returns:
            Retry result
        """
        session = await self.get_session(session_id)
        if not session:
            raise ValueError(f"Session not found: {session_id}")

        if session.status not in [DemoSessionStatus.FAILED, DemoSessionStatus.PARTIAL]:
            raise ValueError(f"Cannot retry session in {session.status.value} state")

        logger.info(
            "Retrying failed cloning",
            session_id=session_id,
            services=services
        )

        # Get base tenant ID from config
        demo_config = settings.DEMO_ACCOUNTS.get(session.demo_account_type)
        base_tenant_id = demo_config.get("base_tenant_id", str(session.base_demo_tenant_id))

        # Trigger new cloning attempt
        result = await self.trigger_orchestrated_cloning(session, base_tenant_id)

        return result