"""
ActivityBreakMixin — break detection, management, and interval processing.

Provides break session start/stop lifecycle and break interval upload for
:class:`~ws_package.controllers.activity_controller.ActivityController`.
All methods assume ``self`` is an ``ActivityController`` instance and have access
to all attributes defined in ``ActivityController.__init__``.

Thread-safety:
    All public methods are async coroutines and must be awaited from the async
    monitoring loop.  ``set_is_break()`` is synchronous and may be called from any
    thread; it only mutates ``self.is_break`` which is a plain Python integer.
"""
from __future__ import annotations

import asyncio
import datetime
from typing import Optional

from ws_package.log.writer import LogWriter
from ws_package.entities.activity_entity import LocationEntity

logger = LogWriter.file(__name__)


class ActivityBreakMixin:
    """Break session start/stop lifecycle and break interval upload for ActivityController."""

    def set_is_break(self, is_break: int) -> None:
        """
        Set break status from Electron.

        Args:
            is_break: 0 = not on break, 1 = on break
        """
        old_value = self.is_break
        self.is_break = is_break
        # Verify the value was set correctly
        if self.is_break != is_break:
            logger.error("CRITICAL: Failed to set is_break! Expected %s, got %s", is_break, self.is_break)
            self.is_break = is_break  # Force set again
        # Reset flush timer when break status changes
        if is_break == 0:
            self._last_break_queue_flush = None  # Reset when break ends
        logger.info("ActivityController: Set is_break from %s to %s (verified: %s)", old_value, is_break, self.is_break)

    MAX_BREAK_HOURS: int = 8  # Safety valve: auto-reset stale break after this many hours

    async def check_stale_break(self) -> None:
        """
        Safety valve for silent-mode / Electron-crash scenarios.

        If is_break=1 has been set for longer than MAX_BREAK_HOURS without a
        break_stop_event being called (e.g. because Electron crashed mid-break),
        automatically reset break state so monitoring continues normally.
        """
        if self.is_break != 1:
            return
        if not hasattr(self, 'break_start_time') or self.break_start_time is None:
            return
        import datetime
        elapsed = (datetime.datetime.now() - self.break_start_time).total_seconds()
        max_seconds = self.MAX_BREAK_HOURS * 3600
        if elapsed > max_seconds:
            logger.warning(
                "[BreakSafety] Break has been active for %.1fh (limit %dh) — "
                "Electron may have crashed. Auto-resetting break state.",
                elapsed / 3600, self.MAX_BREAK_HOURS
            )
            self.set_is_break(0)
            self.break_start_time = None

    async def break_start_event(self):
        """
        Handle break start event - called when user starts a break.
        This is called after last_api_call to ensure all pending data is saved.
        CRITICAL: Uploads all regular intervals (is_uploaded=0, is_break=0) before break starts.
        """
        from ws_package.utils.build_mode import is_silent_build

        if is_silent_build():
            logger.info("[Silent] break_start_event ignored")
            return
        try:
            logger.info("=== break start event() called ===")

            # CRITICAL: Flush queue to ensure any pending intervals are written to DB
            # This ensures intervals saved before break starts are committed
            try:
                from ws_package.services.db_writer_service import db_writer_service
                from ws_package.utils.db_write_queue import db_write_queue, WriteOperationType

                activity_queue_size = db_write_queue.get_queue_size(WriteOperationType.ACTIVITY_INTERVAL)
                total_queue_size = db_write_queue.get_total_queue_size()
                logger.info("Queue status in break_start_event: ACTIVITY_INTERVAL=%s, Total=%s", activity_queue_size, total_queue_size)

                if total_queue_size > 0:
                    logger.info("Flushing %s pending queue items in break_start_event...", total_queue_size)
                    processed_count = await db_writer_service.process_immediately()
                    logger.info("Flushed %s queued database writes in break_start_event", processed_count)
                    # Small delay to ensure commit is visible
                    await asyncio.sleep(0.3)
                else:
                    logger.info("No items in queue during break_start_event")
            except Exception as e:
                logger.error("Error flushing queue in break_start_event: %s", e, exc_info=True)
                # Don't fail break_start_event if queue flush fails - non-critical

            # CRITICAL: Upload all regular intervals (is_uploaded=0, is_break=0) before break starts
            # This ensures no regular intervals are left unuploaded when break starts
            try:
                # Ensure user_entity is set (required for API calls)
                if hasattr(self, 'user_entity') and self.user_entity is not None:
                    logger.info("Checking for regular intervals (is_uploaded=0, is_break=0) to upload before break starts...")

                    # CRITICAL: Filter by current session counter to prevent picking intervals from previous sessions
                    current_session_counter = self.last_count + 1
                    logger.info("Filtering regular intervals by current session counter=%s", current_session_counter)

                    # Query all regular intervals with is_uploaded=0 and is_break=0 from current session only
                    regular_intervals = await self.activity_service.get_activity_intervals(None, None, counter=current_session_counter)

                    if regular_intervals and len(regular_intervals) > 0:
                        logger.info("Found %s regular interval(s) with is_uploaded=0 and is_break=0 - uploading before break starts...", len(regular_intervals))

                        # Set up headers for API calls
                        self.activity_service.headers = {
                            'SDToken': str(self.user_entity.sd_token),
                            'OrgID': str(self.user_entity.org_id),
                            'UserID': str(self.user_entity.user_id),
                            'appVersion': str(self.appVersion),
                            'Authorization': f'Bearer {self.user_entity.sd_token}'
                        }

                        # Process and upload regular intervals
                        sending_intervals = self.process_intervals_bulk(regular_intervals, self.user_entity.org_id, self.user_entity)

                        interval_count = 0
                        async for response in sending_intervals:
                            if not response or not isinstance(response, (list, tuple)):
                                logger.info("No interval found %s", response)
                                continue

                            _response = self.activity_service.push_activity_logs(response[0])
                            logger.info("Response for regular intervals: %s", _response)

                            if _response and _response.get("message") == "Token is Expired":
                                logger.error("Token is Expired - stopping upload")
                                self._send_subscription_expired_popup(_response)
                                self.status = False
                                break

                            elif _response and (_response.get("planStatus") != "active" or _response.get("isdeleted") == 1 or _response.get("isdeactivated") == 1):
                                logger.error("Plan status is not active or user is deleted/deactivated - stopping upload")
                                self._send_subscription_expired_popup(_response)
                                self.status = False
                                break

                            elif _response and _response.get("planStatus") == "active":
                                logger.info("API response indicates success - updating regular intervals as uploaded...")

                                # Update all intervals in this batch as uploaded
                                intervals_to_update = response[1] if len(response) > 1 else []
                                update_count = 0

                                for interval in intervals_to_update:
                                    try:
                                        # If interval already has an ID, use it directly
                                        if hasattr(interval, 'id') and interval.id:
                                            await self.activity_service.update_activity_interval(interval)
                                            update_count += 1
                                        else:
                                            # Query database to find the actual record with ID
                                            from ws_package.models.base import async_session, ActivityInterval
                                            from sqlalchemy import select
                                            from ws_package.utils.db_lock_manager import db_lock_manager

                                            with db_lock_manager.read_lock():
                                                async with async_session() as session:
                                                    query = select(ActivityInterval).where(
                                                        ActivityInterval.start_time == interval.start_time,
                                                        ActivityInterval.end_time == interval.end_time,
                                                        ActivityInterval.is_break == 0,
                                                        ActivityInterval.is_uploaded.is_(False)
                                                    ).order_by(ActivityInterval.id.desc()).limit(1)

                                                    result = await session.execute(query)
                                                    db_interval = result.scalar_one_or_none()

                                                    if db_interval and db_interval.id:
                                                        await self.activity_service.update_activity_interval(db_interval)
                                                        update_count += 1
                                    except Exception as e:
                                        logger.error("Error updating regular interval: %s", e, exc_info=True)

                                if update_count > 0:
                                    logger.info("Successfully updated %s regular interval(s) as uploaded (is_uploaded=1)", update_count)

                                    # Flush queue to ensure updates are committed
                                    try:
                                        from ws_package.services.db_writer_service import db_writer_service
                                        processed_count = await db_writer_service.process_immediately()
                                        logger.info("Flushed %s queued database updates after uploading regular intervals", processed_count)
                                        await asyncio.sleep(0.3)
                                    except Exception as e:
                                        logger.error("Error flushing queue after updating regular intervals: %s", e, exc_info=True)

                                interval_count += 1

                        logger.info("Uploaded %s batch(es) of regular intervals before break starts", interval_count)
                    else:
                        logger.info("No regular intervals with is_uploaded=0 and is_break=0 found - nothing to upload")
                else:
                    logger.warning("user_entity is not set - cannot upload regular intervals. Skipping upload.")
            except Exception as e:
                logger.error("Error uploading regular intervals in break_start_event: %s", e, exc_info=True)
                # Don't fail break_start_event if upload fails - continue with break start

            # Register open break session on activity/log (stop=null, intervals=[])
            try:
                if hasattr(self, "user_entity") and self.user_entity is not None:
                    break_start_time = getattr(self, "break_start_time", None) or datetime.datetime.now()
                    break_start_time = self._normalize_activity_log_start(break_start_time)
                    self.break_start_time = break_start_time
                    current_break_id = getattr(self, "break_id", 0) or 0
                    if current_break_id != 0:
                        await self.push_open_session_activity_log(
                            self.user_entity,
                            start_time=break_start_time,
                            break_id=current_break_id,
                        )
                    else:
                        logger.warning(
                            "Skipping open break session activity/log — break_id not set yet"
                        )
            except Exception as e:
                logger.error(
                    "Error posting open break session activity/log at break start: %s",
                    e,
                    exc_info=True,
                )

            logger.info("Break start event processed - monitoring continues with break intervals (is_break=1)")

            # Create first partial break interval from break_start_time to next slot boundary
            # so the break is recorded as starting at actual time (e.g. 15:33) not the next boundary (15:35)
            try:
                break_start_time = getattr(self, 'break_start_time', None)
                current_break_id = getattr(self, 'break_id', 0) or 0
                if break_start_time is not None and current_break_id != 0:
                    slot_start = self._align_to_interval_boundary(break_start_time)
                    interval_minutes = int(self.interval / 60)
                    slot_end = slot_start + datetime.timedelta(minutes=interval_minutes)
                    if break_start_time < slot_end:
                        partial_start = break_start_time.replace(microsecond=0)
                        partial_end = slot_end.replace(microsecond=0)
                        # Use exact=True so the pre-break interval (whose end_time may have
                        # microseconds just above partial_start) is not mistaken for the
                        # partial break interval we are about to create.
                        existing = await self.activity_service.check_existing_interval(partial_start, partial_end, exact=True)
                        if not existing:
                            try:
                                activity_level = await self.get_activity_level_from_db(partial_start, partial_end)
                            except Exception as e:
                                logger.warning("Error getting activity for first partial break interval: %s", e)
                                activity_level = {'mouse': '0.0', 'average': '0.0', 'keyboard': '0.0'}
                            await self.activity_service.save_activity_log(
                                activity_level, partial_start, partial_end,
                                self.last_count + 1, self.get_today_weekday(),
                                break_id=current_break_id, is_idle=False
                            )
                            logger.info("Created first partial break interval from actual start: %s to %s (break_id=%s)", partial_start, partial_end, current_break_id)
                        else:
                            logger.info("First partial break interval already exists: %s to %s", partial_start, partial_end)
            except Exception as e:
                logger.error("Error creating first partial break interval: %s", e, exc_info=True)

            # CRITICAL: Ensure any intervals saved during break_start are flushed immediately
            try:
                from ws_package.services.db_writer_service import db_writer_service
                from ws_package.utils.db_write_queue import db_write_queue, WriteOperationType

                # Check if there are any new intervals queued after break_start
                activity_queue_size = db_write_queue.get_queue_size(WriteOperationType.ACTIVITY_INTERVAL)
                if activity_queue_size > 0:
                    logger.info("Flushing %s intervals queued during break_start...", activity_queue_size)
                    processed_count = await db_writer_service.process_immediately()
                    logger.info("Flushed %s intervals after break_start", processed_count)
                    await asyncio.sleep(0.2)
            except Exception as e:
                logger.error("Error flushing queue after break_start: %s", e, exc_info=True)

            logger.info("=== break_start_event() completed ===")
        except Exception as e:
            logger.error("Error in break_start_event: %s", e, exc_info=True)
            raise

    async def break_stop_event(self):
        """
        Handle break stop event - called when user stops a break.
        This is called before monitoring resumes.
        Also called from stop_event to ensure any remaining break intervals are sent.
        """
        try:
            # Track when break_stop_event is called to prevent duplicate API calls from stop_event
            self._last_break_stop_event_time = datetime.datetime.now()
            self.Flag = False
            logger.info("=" * 80)
            logger.info("=== BREAK STOP EVENT: Starting break interval processing ===")
            logger.info("=" * 80)

            # Ensure user_entity is set (required for API calls)
            if not hasattr(self, 'user_entity') or self.user_entity is None:
                logger.error("user_entity is not set - cannot process break intervals. break_stop_event requires user_entity to be set.")
                return

            # Get current break_id for logging
            current_break_id = getattr(self, 'break_id', None)
            logger.info("Current break_id from ActivityController: %s", current_break_id)

            # CRITICAL: Before processing break intervals, ensure any dummy entry (start_time == end_time)
            # is updated with the actual break stop time, and ensure it has is_uploaded=False
            if current_break_id and current_break_id != 0:
                try:
                    from ws_package.models.base import async_session, ActivityInterval
                    from sqlalchemy import select
                    from ws_package.utils.db_lock_manager import db_lock_manager

                    # Flush queue first to ensure any pending writes are committed
                    from ws_package.services.db_writer_service import db_writer_service
                    await db_writer_service.process_immediately()
                    await asyncio.sleep(0.2)  # Small delay to ensure commit is visible

                    break_stop_time = datetime.datetime.now()
                    self._break_stop_time = break_stop_time  # So device loop can trim next interval to 17:17-17:20 even if DB update not visible yet
                    logger.info("Checking for dummy break entries (break_id=%s) to update end_time to %s", current_break_id, break_stop_time)

                    with db_lock_manager.read_lock():
                        async with async_session() as session:
                            # Find dummy entries (start_time == end_time) with is_break=break_id and is_uploaded=False
                            query = select(ActivityInterval).where(
                                ActivityInterval.is_break == current_break_id,
                                ActivityInterval.is_uploaded.is_(False)
                            )
                            result = await session.execute(query)
                            break_intervals = result.scalars().all()

                            # Find dummy entry (start_time == end_time) or update the last one
                            dummy_entry = None
                            for interval in break_intervals:
                                if interval.start_time == interval.end_time:
                                    dummy_entry = interval
                                    logger.info("Found dummy entry: id=%s, start_time=%s, end_time=%s", interval.id, interval.start_time, interval.end_time)
                                    break

                            if dummy_entry:
                                # Update dummy entry's end_time to break stop time
                                logger.info("Updating dummy entry (id=%s) end_time from %s to %s", dummy_entry.id, dummy_entry.end_time, break_stop_time)
                                dummy_entry.end_time = break_stop_time
                                # Ensure is_uploaded is False (should already be False, but double-check)
                                if dummy_entry.is_uploaded:
                                    logger.warning("Dummy entry (id=%s) has is_uploaded=True, setting to False", dummy_entry.id)
                                    dummy_entry.is_uploaded = False

                                # Commit the update
                                await session.commit()
                                logger.info("Updated dummy entry (id=%s) - end_time set to break stop time", dummy_entry.id)

                                # Flush queue again to ensure update is visible
                                await db_writer_service.process_immediately()
                                await asyncio.sleep(0.2)
                            elif break_intervals:
                                # No dummy entry, but there are break intervals
                                # Check if the last interval's end_time needs to be updated
                                last_interval = max(break_intervals, key=lambda x: x.end_time)
                                time_since_last = (break_stop_time - last_interval.end_time).total_seconds()

                                if time_since_last > 0:
                                    # For small gaps (<= 30 seconds), update the last interval's end_time
                                    # instead of creating a new interval to avoid unnecessary small intervals
                                    if time_since_last <= 30:
                                        logger.info("Updating last break interval end_time from %s to %s (gap: %ss)", last_interval.end_time, break_stop_time, time_since_last)
                                        last_interval.end_time = break_stop_time
                                        # Ensure is_uploaded is False
                                        if last_interval.is_uploaded:
                                            logger.warning("Last interval (id=%s) has is_uploaded=True, setting to False", last_interval.id)
                                            last_interval.is_uploaded = False

                                        # Commit the update
                                        await session.commit()
                                        logger.info("Updated last break interval (id=%s) - end_time set to break stop time", last_interval.id)

                                        # Flush queue to ensure update is visible
                                        await db_writer_service.process_immediately()
                                        await asyncio.sleep(0.2)
                                    else:
                                        # For larger gaps (> 30 seconds), create a new interval
                                        logger.info("Creating new break interval from %s to %s (gap: %ss)", last_interval.end_time, break_stop_time, time_since_last)
                                        empty_activity_level = {'mouse': '0.0', 'average': '0.0', 'keyboard': '0.0'}
                                        await self.activity_service.save_activity_log(
                                            empty_activity_level,
                                            last_interval.end_time,
                                            break_stop_time,
                                            self.last_count+1,
                                            self.get_today_weekday(),
                                            break_id=current_break_id
                                        )
                                        # Flush queue to ensure new interval is committed
                                        await db_writer_service.process_immediately()
                                        logger.info("Created new break interval for remaining time")
                                        # Short wait to ensure interval is committed and visible in database
                                        await asyncio.sleep(0.5)
                            else:
                                # No break intervals found at all - create one from break_start_time to break_stop_time (not session start)
                                logger.warning("No break intervals found for break_id=%s - creating fallback break interval", current_break_id)
                                break_start_time_attr = getattr(self, 'break_start_time', None)
                                interval_start = break_start_time_attr if break_start_time_attr else (self.start_date if hasattr(self, 'start_date') and self.start_date else break_stop_time - datetime.timedelta(seconds=1))
                                if interval_start >= break_stop_time:
                                    interval_start = break_stop_time - datetime.timedelta(seconds=1)
                                logger.info("Creating break interval from break_start_time=%s to break_stop_time=%s", interval_start, break_stop_time)
                                empty_activity_level = {'mouse': '0.0', 'average': '0.0', 'keyboard': '0.0'}
                                await self.activity_service.save_activity_log(
                                    empty_activity_level,
                                    interval_start,
                                    break_stop_time,
                                    self.last_count+1,
                                    self.get_today_weekday(),
                                    break_id=current_break_id
                                )
                                # Flush queue to ensure new interval is committed
                                await db_writer_service.process_immediately()
                                logger.info("Created break interval from break_start_time to break_stop_time")
                                # Short wait to ensure interval is committed and visible in database
                                await asyncio.sleep(0.5)
                except Exception as e:
                    logger.error("Error updating dummy entry or creating partial interval: %s", e, exc_info=True)
                    # Continue with break_stop_event even if this fails

            # Set up headers for API calls
            self.activity_service.headers = {
                'SDToken': str(self.user_entity.sd_token),
                'OrgID': str(self.user_entity.org_id),
                'UserID': str(self.user_entity.user_id),
                'appVersion': str(self.appVersion),
                'Authorization': f'Bearer {self.user_entity.sd_token}'
            }
            logger.info("API headers configured - OrgID: %s, UserID: %s", self.user_entity.org_id, self.user_entity.user_id)

            # CRITICAL: If we just created a break interval, verify it's in the database before querying
            if current_break_id and current_break_id != 0:
                logger.info("Verifying break interval is visible in database before querying...")
                max_verification_retries = 3
                verification_delay = 1.0
                interval_verified = False

                for verify_attempt in range(max_verification_retries):
                    try:
                        from ws_package.models.base import async_session, ActivityInterval
                        from sqlalchemy import select
                        from ws_package.utils.db_lock_manager import db_lock_manager

                        with db_lock_manager.read_lock():
                            async with async_session() as session:
                                # Query for break intervals with this break_id
                                query = select(ActivityInterval).where(
                                    ActivityInterval.is_break == current_break_id,
                                    ActivityInterval.is_uploaded.is_(False)
                                )
                                result = await session.execute(query)
                                found_intervals = result.scalars().all()

                                if found_intervals and len(found_intervals) > 0:
                                    interval_verified = True
                                    logger.info("Verified %s break interval(s) in database (attempt %s)", len(found_intervals), verify_attempt + 1)
                                    break
                                else:
                                    logger.warning("Break interval not found in database (attempt %s/%s)", verify_attempt + 1, max_verification_retries)
                    except Exception as e:
                        logger.error("Error verifying break interval: %s", e, exc_info=True)

                    if verify_attempt < max_verification_retries - 1 and not interval_verified:
                        logger.info("Waiting %ss before retrying verification...", verification_delay)
                        await asyncio.sleep(verification_delay)
                        verification_delay += 0.5  # Increase delay for each retry

                if not interval_verified:
                    logger.warning("Break interval not verified in database after retries, but proceeding with query anyway...")

            # CRITICAL: Process ALL break intervals from database (is_break != 0 and is_uploaded=False)
            # This works regardless of whether break_id is set, as it queries the database directly
            logger.info("=" * 80)
            logger.info("Processing all remaining break intervals from database...")
            logger.info("=" * 80)
            interval_count = 0
            async for response in self.process_break_interval():
                if not response or not isinstance(response, (list, tuple)):
                    logger.warning("No break interval found in response: %s", response)
                    continue

                payload = response[0]
                interval_payloads = response[1] if len(response) > 1 else []
                intervals_orm = response[2] if len(response) > 2 else []  # Original ORM objects for updating

                # Log payload details before sending
                if payload and len(payload) > 0:
                    payload_info = payload[0]
                    logger.info("-" * 80)
                    logger.info("Preparing to send break intervals to API:")
                    logger.info("  Break ID: %s", payload_info.get('break_id', 'N/A'))
                    logger.info("  Project ID: %s", payload_info.get('projectId', 'N/A'))
                    logger.info("  Task ID: %s", payload_info.get('todo_id', 'N/A'))
                    logger.info("  Start Time: %s", payload_info.get('start', 'N/A'))
                    logger.info("  Stop Time: %s", payload_info.get('stop', 'N/A'))
                    logger.info("  Timezone: %s", payload_info.get('timezone', 'N/A'))
                    logger.info("  Number of intervals: %s", len(interval_payloads))
                    logger.info("  Device Type: %s", payload_info.get('deviceType', 'N/A'))
                    logger.info("-" * 80)

                    # Log each interval detail
                    for idx, interval_payload in enumerate(interval_payloads, 1):
                        logger.info("  Interval %s: Entry=%s, From=%s, To=%s",
                                  idx, interval_payload.get('Entry', 'N/A'),
                                  interval_payload.get('from', 'N/A'), interval_payload.get('to', 'N/A'))

                # Send to API
                logger.info("=" * 80)
                logger.info("Sending break intervals to activity/log API...")
                logger.info("Payload contains %s break interval group(s)", len(payload))
                logger.info("=" * 80)
                _response = self.activity_service.push_activity_logs(payload)
                logger.info("=" * 80)
                logger.info("API call completed for break intervals")
                logger.info("=" * 80)

                if _response is None:
                    logger.error("API response is None - break intervals may not have been sent successfully")
                    continue

                logger.info("API Response received:")
                logger.info("  Message: %s", _response.get('message', 'N/A'))
                logger.info("  Plan Status: %s", _response.get('planStatus', 'N/A'))
                logger.info("  Is Deleted: %s", _response.get('isdeleted', 'N/A'))
                logger.info("  Is Deactivated: %s", _response.get('isdeactivated', 'N/A'))

                if _response and _response.get("message") == "Token is Expired":
                    logger.error("Token is Expired - stopping activity monitoring")
                    self._send_subscription_expired_popup(_response)
                    self.status = False
                    break

                elif _response and (_response.get("planStatus") != "active" or _response.get("isdeleted") == 1 or _response.get("isdeactivated") == 1):
                    logger.error("Plan status is not active or user is deleted/deactivated - stopping activity monitoring")
                    logger.error("  Plan Status: %s, Is Deleted: %s, Is Deactivated: %s", _response.get('planStatus'), _response.get('isdeleted'), _response.get('isdeactivated'))
                    self._send_subscription_expired_popup(_response)
                    self.status = False
                    break

                elif _response and _response.get("planStatus") == "active":
                    logger.info("API response indicates success - updating intervals and screenshots...")

                    # Update activity intervals - use ORM objects, not payload dictionaries
                    update_count = 0
                    if intervals_orm and len(intervals_orm) > 0:
                        logger.info("Updating %s break intervals to mark as uploaded...", len(intervals_orm))
                        for interval_orm in intervals_orm:
                            try:
                                if interval_orm and hasattr(interval_orm, 'id'):
                                    await self.activity_service.update_activity_interval(interval_orm)
                                    update_count += 1
                                    logger.info("  Marked interval (id=%s) as uploaded", interval_orm.id)
                                else:
                                    logger.warning("  Invalid interval object: %s", interval_orm)
                            except Exception as e:
                                logger.error("  Error updating interval (id=%s): %s", getattr(interval_orm, 'id', 'N/A'), e, exc_info=True)
                        logger.info("Successfully updated %s/%s break intervals in database (is_uploaded=1)", update_count, len(intervals_orm))

                        # CRITICAL: Flush queue immediately to ensure is_uploaded=1 is committed to database
                        try:
                            from ws_package.services.db_writer_service import db_writer_service
                            logger.info("Flushing database write queue to commit interval updates...")
                            processed_count = await db_writer_service.process_immediately()
                            logger.info("Flushed %s queued database writes after updating break intervals", processed_count)
                            # Small delay to ensure commit is visible
                            await asyncio.sleep(0.3)
                        except Exception as e:
                            logger.error("Error flushing queue after updating break intervals: %s", e, exc_info=True)
                    else:
                        logger.warning("No ORM interval objects available for update - intervals may not be marked as uploaded")
                        # Fallback: try to fetch intervals from database using IDs from payloads
                        if interval_payloads:
                            logger.info("Attempting fallback: fetching intervals from database by ID...")
                            try:
                                from ws_package.models.base import async_session, ActivityInterval
                                from sqlalchemy import select
                                from ws_package.utils.db_lock_manager import db_lock_manager

                                interval_ids = [ip.get('Entry') for ip in interval_payloads if ip.get('Entry')]
                                if interval_ids:
                                    with db_lock_manager.read_lock():
                                        async with async_session() as session:
                                            query = select(ActivityInterval).where(ActivityInterval.id.in_(interval_ids))
                                            result = await session.execute(query)
                                            fetched_intervals = result.scalars().all()

                                            for fetched_interval in fetched_intervals:
                                                try:
                                                    await self.activity_service.update_activity_interval(fetched_interval)
                                                    update_count += 1
                                                    logger.info("  Fallback: Marked interval (id=%s) as uploaded", fetched_interval.id)
                                                except Exception as e:
                                                    logger.error("  Fallback error updating interval (id=%s): %s", fetched_interval.id, e, exc_info=True)
                                    logger.info("Fallback: Successfully updated %s/%s break intervals", update_count, len(interval_ids))

                                    # CRITICAL: Flush queue after fallback updates too
                                    if update_count > 0:
                                        try:
                                            from ws_package.services.db_writer_service import db_writer_service
                                            logger.info("Flushing database write queue after fallback updates...")
                                            processed_count = await db_writer_service.process_immediately()
                                            logger.info("Flushed %s queued database writes after fallback updates", processed_count)
                                            await asyncio.sleep(0.3)
                                        except Exception as e:
                                            logger.error("Error flushing queue after fallback updates: %s", e, exc_info=True)
                            except Exception as fallback_error:
                                logger.error("Fallback update failed: %s", fallback_error, exc_info=True)

                    # Update screenshots
                    screenshot_count = 0
                    for interval_payload in payload:
                        for interval in interval_payload.get('intervals', []):
                            screen_url = interval.get("screenUrl")
                            if screen_url:
                                try:
                                    await self.screenshot_controller.update_screenshots(screen_url, self.last_count+1)
                                    screenshot_count += 1
                                except Exception as e:
                                    logger.error("Error updating screenshot for URL=%s: %s", screen_url, e)
                    logger.info("Successfully updated %s screenshots", screenshot_count)

                    interval_count += 1
                    logger.info("Break interval batch %s processed successfully", interval_count)
                else:
                    logger.warning("Unexpected API response format: %s", _response)

            logger.info("=" * 80)
            logger.info("=== BREAK STOP EVENT: Completed - Processed %s break interval batch(es) ===", interval_count)
            logger.info("=" * 80)

            # CRITICAL: Restart device listeners after break ends
            # This ensures listeners are working when monitoring resumes
            if hasattr(self, 'device_listener') and self.device_listener:
                try:
                    logger.info("Restarting device listeners after break_stop_event...")
                    self.device_listener.restart_listeners()
                    logger.info("Device listeners restarted after break_stop_event - ready for tracking resume")
                except Exception as e:
                    logger.error("Error restarting device listeners after break_stop_event: %s", e, exc_info=True)

        except Exception as e:
            logger.error("=" * 80)
            logger.error("ERROR in break_stop_event: %s", e, exc_info=True)
            logger.error("=" * 80)
            raise

    async def process_break_interval(self):
        """
        Process break intervals from database and prepare payload for API.
        This is an async generator that yields payload and interval_payloads.
        """
        try:
            logger.info("=== process_break_interval() called ===")
            logger.info("Fetching break intervals from database...")

            # CRITICAL: Filter by current session counter to prevent picking break intervals from previous sessions
            current_session_counter = self.last_count + 1
            logger.info("Filtering break intervals by current session counter=%s", current_session_counter)

            intervals = await self.activity_service.get_break_intervals(counter=current_session_counter)

            if intervals is None:
                logger.warning("get_break_intervals() returned None")
                yield None
                return

            interval_count = len(intervals) if intervals else 0
            logger.info("Found %s break interval(s) in database", interval_count)

            if interval_count == 0:
                logger.info("No break intervals found in database - nothing to send")
                yield None
                return

            # Log interval details
            logger.info("Break intervals from database:")
            for idx, interval in enumerate(intervals, 1):
                logger.info("  Interval %s: ID=%s, is_break=%s, start_time=%s, end_time=%s, is_uploaded=%s",
                          idx, interval.id, interval.is_break,
                          interval.start_time.strftime('%Y-%m-%d %H:%M:%S'),
                          interval.end_time.strftime('%Y-%m-%d %H:%M:%S'), interval.is_uploaded)

            # CRITICAL: Sort intervals by start_time ascending so that the earliest start time
            # is always intervals[0]. Without this, a sleep-wake event can insert a later-ID
            # interval with an earlier start_time (e.g. 18:02) AFTER an interval with a later
            # start_time (e.g. 18:10), causing the payload to use the wrong start time.
            intervals = sorted(intervals, key=lambda x: x.start_time)
            logger.info("Intervals sorted by start_time ascending for correct payload start/stop times")

            # Get break_id from the first interval's is_break field (which stores break_id value)
            break_id_from_interval = intervals[0].is_break if intervals and len(intervals) > 0 else 0
            logger.info("Using break_id=%s from database (stored in is_break field)", break_id_from_interval)

            # CRITICAL: Filter out intervals that start before the payload start time.
            # General rule: No interval start time should be earlier than its payload start time.
            # Prefer pinned open-session start so empty registration and interval upload match exactly.
            if intervals:
                pinned_start = self._get_pinned_activity_log_session_start()
                payload_start_time = (
                    pinned_start
                    if pinned_start is not None
                    else self._normalize_activity_log_start(intervals[0].start_time)
                )
                filtered_intervals = []
                filtered_count = 0
                for interval in intervals:
                    if interval.start_time < payload_start_time:
                        filtered_count += 1
                        logger.warning("Filtering out break interval (ID=%s, start=%s, end=%s) - starts before payload start time (%s)", interval.id, interval.start_time, interval.end_time, payload_start_time)
                    else:
                        filtered_intervals.append(interval)

                if filtered_count > 0:
                    logger.warning("Filtered out %s break interval(s) that start before payload start time (%s)", filtered_count, payload_start_time)
                    intervals = filtered_intervals
                    # Update break_id from the first interval after filtering
                    if intervals:
                        break_id_from_interval = intervals[0].is_break
                        logger.info("Updated break_id=%s after filtering", break_id_from_interval)

                if not intervals:
                    logger.error("No break intervals remain after filtering - nothing to send")
                    yield None
                    return

                interval_count = len(intervals)

            # Process each interval and build payloads
            interval_payloads = []
            logger.info("Building interval payloads...")

            for idx, interval in enumerate(intervals, 1):
                logger.info("Processing interval %s/%s: Entry=%s, Time=%s to %s",
                          idx, interval_count, interval.id,
                          interval.start_time.strftime('%Y-%m-%d %H:%M:%S'),
                          interval.end_time.strftime('%Y-%m-%d %H:%M:%S'))

                try:
                    from ws_package.utils.system import get_ip, get_os_version
                    payload_from, payload_to = self._payload_interval_bounds(interval, payload_start_time)
                    if payload_to is not None and payload_from is not None and payload_to <= payload_from:
                        logger.info(
                            "Skipping break interval (id=%s, start=%s, end=%s) — no time left after clipping to payload start %s",
                            interval.id,
                            interval.start_time,
                            interval.end_time,
                            payload_start_time,
                        )
                        continue
                    screen_url = await self.ss(payload_from, payload_to, self.user_entity)
                    activity_level = await self.get_activity_level_from_db(payload_from, payload_to)
                    app_and_urls = await self.activity_service.get_apps_urls(payload_from, payload_to, self.appandurlstatus)

                    interval_payload = {
                        "Entry": interval.id,
                        "from": payload_from.strftime("%Y-%m-%d %H:%M:%S"),
                        "to": payload_to.strftime("%Y-%m-%d %H:%M:%S"),
                        "screenUrl": screen_url,
                        "activityLevel": activity_level,
                        "appAndUrls": app_and_urls,
                        "deviceType": "Break",
                        "break_id": interval.is_break,
                        "os_type": self.get_os_type_caps(),
                        "note": None,
                        "idle_note_url": None,
                        "idleInterval": None,
                        "location": LocationEntity(
                            long="null",
                            lat="null"
                        ).model_dump(),
                        "interval_time_db": int(self.interval/60),
                        "has_on_demand_screenshot": getattr(interval, "has_on_demand_screenshot", False),
                    }
                    interval_payloads.append(interval_payload)
                    logger.info("  Interval %s payload created successfully", idx)
                except Exception as e:
                    logger.error("  Error processing interval %s (Entry=%s): %s", idx, interval.id, e, exc_info=True)
                    # Continue with other intervals even if one fails
                    continue

            if not interval_payloads:
                logger.error("No interval payloads were created successfully - nothing to send")
                yield None
                return

            # Build the final payload for this day
            logger.info("Building final payload for API...")
            from ws_package.utils.system import get_ip, get_os_version
            pinned_start = self._get_pinned_activity_log_session_start()
            if pinned_start is not None:
                payload_start_time = pinned_start.strftime("%Y-%m-%d %H:%M:%S")
            else:
                payload_start_time = interval_payloads[0]["from"]
            payload_stop_time = intervals[-1].end_time.strftime("%Y-%m-%d %H:%M:%S")
            timezone_str = str(self.timezone_manager.local_timezone) if self.timezone_manager.local_timezone else str(self.get_system_timezone())

            # Use break_project_id if available (stored when break started), otherwise use current_project_id, fallback to "1001"
            project_id_for_break = getattr(self, 'break_project_id', None) or self.current_project_id or "1001"
            task_id_for_break = getattr(self, 'break_task_id', None) or self.current_task_id

            payload = [{
                "projectId": project_id_for_break,
                "start": payload_start_time,
                "stop": payload_stop_time,
                "selfi_id": None,
                "timezone": self._normalize_timezone(str(self.timezone_manager.local_timezone) if self.timezone_manager.local_timezone else str(self.get_system_timezone())),
                "break_id": break_id_from_interval,  # Use is_break value from interval (which stores break_id)
                "deviceType": "Break",
                "os_type": self.get_os_type_caps(),
                "selfiVerification": True,
                "intervals": interval_payloads,
                "time_type": "1",
                "interval_time": int(self.interval/60),
                "source_type": "4",
                "org_id": str(self.user_entity.org_id),
                "todo_id": task_id_for_break,
                "deviceId": self.system_unique_id,
                "ip_address": str(get_ip()),
                "os_version": get_os_version()
            }]

            logger.info("Final payload created successfully:")
            logger.info("  Break ID: %s", break_id_from_interval)
            logger.info("  Project ID: %s (break_project_id=%s, current_project_id=%s)", project_id_for_break, getattr(self, 'break_project_id', None), self.current_project_id)
            logger.info("  Task ID: %s (break_task_id=%s, current_task_id=%s)", task_id_for_break, getattr(self, 'break_task_id', None), self.current_task_id)
            logger.info("  Start: %s", payload_start_time)
            logger.info("  Stop: %s", payload_stop_time)
            logger.info("  Timezone: %s", timezone_str)
            logger.info("  Total intervals in payload: %s", len(interval_payloads))
            logger.info("=== process_break_interval() completed successfully ===")

            # Yield payload, interval_payloads, and original intervals (for updating is_uploaded)
            yield payload, interval_payloads, intervals

        except Exception as e:
            logger.error("=" * 80)
            logger.error("ERROR in process_break_interval: %s", e, exc_info=True)
            logger.error("=" * 80)
            raise
