# repository/ws_activity_repo.py

import asyncio
import datetime
from typing import Optional
from sqlalchemy import select
from ws_package.models.base import async_session, Appmonitor, ActivityInterval
from ws_package.entities.activity_entity import ActivityIntervalEntity
from ws_package.log.writer import LogWriter
from sqlalchemy import func
from ws_package.utils.db_lock_manager import db_lock_manager
from ws_package.utils.db_write_queue import db_write_queue, WriteOperationType

logger = LogWriter.file(__name__)

# Database operation timeout (5 seconds)
DB_OPERATION_TIMEOUT = 5.0


class ActivityManager:
    # Removed individual db_write_lock - now using centralized db_lock_manager
    async def get_app_monitors(self, start_date: Optional[datetime.datetime] = None, end_date: Optional[datetime.datetime] = None):
        """
        Get app monitor records from the database.
        This is a read-only operation, so it uses read_lock() to allow concurrent reads.
        """
        query = select(Appmonitor)
        # Filter based on start and end date
        if start_date:
            query = query.where(Appmonitor.timestamp >= start_date)
            
        if end_date:
            query = query.where(Appmonitor.timestamp <= end_date)
        
        try:
            # Use centralized read lock for database read operations
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    # Fetch the records that match the filters
                    app_monitors_result = await asyncio.wait_for(
                        session.scalars(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    
                    # Convert ScalarResult to a list
                    app_monitors = list(app_monitors_result)
                    
                    if app_monitors:
                        logger.debug("Retrieved %s app monitor record(s)", len(app_monitors))
                    else:
                        logger.debug("No app monitors found matching the criteria")
                    
                    return app_monitors

        except asyncio.TimeoutError:
            logger.error("Database operation timed out after %ss while getting app monitors", DB_OPERATION_TIMEOUT)
            return []
        except Exception as e:
            logger.error("Error getting app monitors: %s", e, exc_info=True)
            return []
    
    async def save_activity_interval(self, activity_interval_entity: ActivityIntervalEntity):
        """
        Save activity interval to in-memory queue.
        The database writer thread will process this asynchronously.
        
        Returns:
            True if enqueued successfully, False otherwise
        """
        try:
            # Enqueue to in-memory queue (non-blocking, no SQLite operation)
            success = db_write_queue.enqueue(
                operation_type=WriteOperationType.ACTIVITY_INTERVAL,
                table_name="activity_interval",
                data=activity_interval_entity.model_dump()
            )
            
            if success:
                logger.debug(
                    f"Enqueued ActivityInterval: {activity_interval_entity.start_time} to {activity_interval_entity.end_time}"
                )
                # Return a mock object for backward compatibility
                # The actual database write happens asynchronously
                return activity_interval_entity
            else:
                logger.error("Failed to enqueue ActivityInterval - queue may be full")
                return False
                
        except Exception as e:
            logger.error("Error enqueueing ActivityInterval: %s", e, exc_info=True)
            return False
    
    async def get_activity_interval(self, start_date: Optional[datetime.datetime] = None, end_date: Optional[datetime.datetime] = None, counter: Optional[int] = None):
        """Fetch unuploaded, non-break activity intervals from the database.

        Args:
            start_date: Optional lower bound for interval start_time (not currently filtered).
            end_date: Optional upper bound for interval end_time (not currently filtered).
            counter: If provided, restrict results to this session counter.

        Returns:
            Sequence of ActivityInterval ORM objects; empty list on error or timeout.
        """
        # Use centralized read lock for database read operations
        try:
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    query = select(ActivityInterval).where(
                        ActivityInterval.is_uploaded.is_(False),
                        ActivityInterval.is_break.is_(0)
                        )
                    
                    # CRITICAL: Filter by counter if provided to prevent picking intervals from previous sessions
                    if counter is not None:
                        query = query.where(ActivityInterval.counter == counter)
                        logger.debug("Filtering activity intervals by counter=%s", counter)
                    
                    # Exclude intervals that are in the update queue to prevent duplicate sends
                    # This ensures intervals being updated are not sent again before the update is committed
                    try:
                        from ws_package.utils.db_write_queue import db_write_queue, WriteOperationType
                        update_queue_items = db_write_queue.get_pending_items(
                            WriteOperationType.UPDATE_ACTIVITY_INTERVAL,
                            limit=1000
                        )
                        
                        if update_queue_items:
                            updating_ids = {item.record_id for item in update_queue_items if item.record_id}
                            if updating_ids:
                                logger.debug("Excluding %s intervals in update queue from query", len(updating_ids))
                                query = query.where(~ActivityInterval.id.in_(updating_ids))
                    except Exception as queue_check_error:
                        # If queue check fails, continue with normal query (non-critical)
                        logger.debug("Could not check update queue (non-critical): %s", queue_check_error)
                    
                    result = await asyncio.wait_for(
                        session.scalars(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    return result.all()
        except asyncio.TimeoutError:
            logger.error("Database read operation timed out after %ss while getting activity interval", DB_OPERATION_TIMEOUT)
            return []
        except Exception as e:
            logger.error("Failed to get activity interval (counter=%s): %s", counter, e, exc_info=True)
            return []

    async def get_break_intervals(self, counter: Optional[int] = None):
        """Fetch unuploaded break intervals from the database, ordered by start_time ascending.

        Args:
            counter: If provided, restrict results to this session counter.

        Returns:
            Sequence of ActivityInterval ORM objects; empty list on error or timeout.
        """
        # Use centralized read lock for database read operations
        try:
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    query = select(ActivityInterval).where(
                        ActivityInterval.is_uploaded.is_(False),
                        ActivityInterval.is_break != 0
                        ).order_by(ActivityInterval.start_time.asc())
                    
                    # CRITICAL: Filter by counter if provided to prevent picking break intervals from previous sessions
                    if counter is not None:
                        query = query.where(ActivityInterval.counter == counter)
                        logger.debug("Filtering break intervals by counter=%s", counter)
                    
                    # Exclude intervals that are in the update queue to prevent duplicate sends
                    # This ensures intervals being updated are not sent again before the update is committed
                    try:
                        from ws_package.utils.db_write_queue import db_write_queue, WriteOperationType
                        update_queue_items = db_write_queue.get_pending_items(
                            WriteOperationType.UPDATE_ACTIVITY_INTERVAL,
                            limit=1000
                        )
                        
                        if update_queue_items:
                            updating_ids = {item.record_id for item in update_queue_items if item.record_id}
                            if updating_ids:
                                logger.debug("Excluding %s intervals in update queue from query", len(updating_ids))
                                query = query.where(~ActivityInterval.id.in_(updating_ids))
                    except Exception as queue_check_error:
                        # If queue check fails, continue with normal query (non-critical)
                        logger.debug("Could not check update queue (non-critical): %s", queue_check_error)
                    
                    result = await asyncio.wait_for(
                        session.scalars(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    return result.all()
        except asyncio.TimeoutError:
            logger.error("Database read operation timed out after %ss while getting break intervals", DB_OPERATION_TIMEOUT)
            return []
        except Exception as e:
            logger.error("Failed to get break intervals (counter=%s): %s", counter, e, exc_info=True)
            return []


    async def sevendays(self, target = 1008):
        """Return the oldest unuploaded intervals beyond the rolling target count.

        If the total number of unuploaded intervals is <= target, returns None (nothing to
        purge). Otherwise returns the oldest ``(total - target)`` records so callers can
        upload or discard them.

        Args:
            target: Maximum number of unuploaded intervals to retain (default: 1008).

        Returns:
            Sequence of ActivityInterval ORM objects to clean up, or None if within limit.
        """
        # Use centralized read lock for database read operations
        try:
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    total_entries = await asyncio.wait_for(
                        session.scalar(
                            select(func.count()).where(ActivityInterval.is_uploaded.is_(False))
                        ),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    # Calculate the number of entries to fetch (oldest remaining ones)
                    remaining_entries = max(0, total_entries - target)
                    if remaining_entries == 0:
                        return None
                    # Fetch the oldest 'remaining_entries' records ordered by start_time (ascending)
                    query = (
                        select(ActivityInterval)
                        .where(ActivityInterval.is_uploaded.is_(False))
                        .order_by(ActivityInterval.start_time)  # Oldest first
                        .limit(remaining_entries)  # Get the required number of records
                    )
                    logger.info("System is in net off in the last 7 days")
                    result = await asyncio.wait_for(
                        session.scalars(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    return result.all()
        except asyncio.TimeoutError:
            logger.error("Database read operation timed out after %ss while getting sevendays", DB_OPERATION_TIMEOUT)
            return None
        except Exception as e:
            logger.error("Failed to get sevendays intervals (target=%s): %s", target, e, exc_info=True)
            return None
    
    async def update_activity_interval(self, activity_interval: ActivityInterval):
        """
        Update activity interval via in-memory queue.
        The database writer thread will process this asynchronously.
        
        Args:
            activity_interval: ActivityInterval ORM object with id attribute
            
        Returns:
            activity_interval object for backward compatibility
        """
        try:
            # Extract record ID
            record_id = activity_interval.id if hasattr(activity_interval, 'id') else None
            if not record_id:
                logger.error("update_activity_interval requires activity_interval with id")
                return False
            
            # Prepare update data
            update_data = {'is_uploaded': True}
            
            # Enqueue update operation (non-blocking, no SQLite)
            success = db_write_queue.enqueue(
                operation_type=WriteOperationType.UPDATE_ACTIVITY_INTERVAL,
                table_name="activity_interval",
                data=update_data,
                record_id=record_id
            )
            
            if success:
                logger.debug("Enqueued ActivityInterval update for id: %s", record_id)
                # Return original object for backward compatibility
                return activity_interval
            else:
                logger.error("Failed to enqueue ActivityInterval update - queue may be full")
                return False
                
        except Exception as e:
            logger.error("Error enqueueing ActivityInterval update: %s", e, exc_info=True)
            return False
    
    async def update_activity_interval_is_idle(self, activity_interval: ActivityInterval):
        """
        Update activity interval is_idle field via in-memory queue.
        The database writer thread will process this asynchronously.
        
        Args:
            activity_interval: ActivityInterval ORM object with id and is_idle attributes
            
        Returns:
            activity_interval object if successful, False otherwise
        """
        try:
            # Extract record ID
            record_id = activity_interval.id if hasattr(activity_interval, 'id') else None
            if not record_id:
                logger.error("update_activity_interval_is_idle requires activity_interval with id")
                return False
            
            # Extract is_idle value
            is_idle = activity_interval.is_idle if hasattr(activity_interval, 'is_idle') else False
            
            # Prepare update data
            update_data = {'is_idle': is_idle}
            
            # Enqueue update operation (non-blocking, no SQLite)
            success = db_write_queue.enqueue(
                operation_type=WriteOperationType.UPDATE_ACTIVITY_INTERVAL,
                table_name="activity_interval",
                data=update_data,
                record_id=record_id
            )
            
            if success:
                logger.debug("Enqueued ActivityInterval is_idle update for id: %s, is_idle=%s", record_id, is_idle)
                # Return original object for backward compatibility
                return activity_interval
            else:
                logger.error("Failed to enqueue ActivityInterval is_idle update - queue may be full")
                return False
                
        except Exception as e:
            logger.error("Error enqueueing ActivityInterval is_idle update: %s", e, exc_info=True)
            return False
    

activity_manager = ActivityManager()
