"""Session synchronization service.

Provides background session synchronization from Claude Code
(~/.claude/projects/) to Frago session storage (~/.frago/sessions/).
"""

import asyncio
import logging
import threading
import time
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)

# Debounce delay for refresh requests
DEBOUNCE_DELAY_SECONDS = 2.0

# Sync interval in seconds (same as deprecated GUI for fast session discovery)
SYNC_INTERVAL_SECONDS = 5


class SyncService:
    """Background session sync service."""

    _instance: Optional["SyncService"] = None
    _lock = threading.Lock()

    def __init__(self) -> None:
        """Initialize the sync service."""
        self._task: Optional[asyncio.Task] = None
        self._stop_event = asyncio.Event()
        self._last_result: Optional[Dict[str, Any]] = None
        self._cache_service: Optional[Any] = None

        # Debounce state
        self._pending_refresh: bool = False
        self._last_refresh_time: float = 0.0
        self._debounce_task: Optional[asyncio.Task] = None

    def set_cache_service(self, cache_service: Any) -> None:
        """Set the cache service for triggering refreshes.

        Args:
            cache_service: CacheService instance
        """
        self._cache_service = cache_service

    @classmethod
    def get_instance(cls) -> "SyncService":
        """Get singleton instance.

        Returns:
            SyncService instance
        """
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = cls()
        return cls._instance

    async def start(self) -> None:
        """Start background sync task."""
        if self._task is not None and not self._task.done():
            logger.warning("Sync service already running")
            return

        self._stop_event.clear()
        self._task = asyncio.create_task(self._sync_loop())
        logger.info(f"Session sync started (interval: {SYNC_INTERVAL_SECONDS}s)")

    async def stop(self) -> None:
        """Stop background sync task."""
        if self._task is None or self._task.done():
            return

        self._stop_event.set()
        self._task.cancel()

        try:
            await self._task
        except asyncio.CancelledError:
            pass

        self._task = None
        logger.info("Session sync stopped")

    async def _sync_loop(self) -> None:
        """Background sync loop."""
        while not self._stop_event.is_set():
            try:
                # Run sync in thread pool to avoid blocking
                result = await asyncio.get_event_loop().run_in_executor(
                    None, self._do_sync
                )
                self._last_result = result

                # Check if there are changes
                has_changes = result.get("synced", 0) > 0 or result.get("updated", 0) > 0

                if has_changes:
                    logger.info(
                        f"Session sync: synced={result.get('synced', 0)}, "
                        f"updated={result.get('updated', 0)}"
                    )

                    # Request debounced cache refresh
                    await self.request_refresh()

            except Exception as e:
                logger.warning(f"Session sync failed: {e}")

            # Wait for next sync interval or stop event
            try:
                await asyncio.wait_for(
                    self._stop_event.wait(),
                    timeout=SYNC_INTERVAL_SECONDS,
                )
                # If wait completes without timeout, stop was requested
                break
            except asyncio.TimeoutError:
                # Timeout means continue to next sync
                continue

    def _do_sync(self) -> Dict[str, Any]:
        """Perform synchronization (runs in thread pool).

        Returns:
            Sync result dictionary
        """
        from frago.session.sync import sync_all_projects

        result = sync_all_projects()

        return {
            "synced": result.synced,
            "updated": result.updated,
            "skipped": result.skipped,
            "errors": result.errors,
        }

    def get_last_result(self) -> Optional[Dict[str, Any]]:
        """Get the last sync result.

        Returns:
            Last sync result or None
        """
        return self._last_result

    async def request_refresh(self) -> None:
        """Request a debounced cache refresh.

        Multiple calls within DEBOUNCE_DELAY_SECONDS will be coalesced
        into a single refresh operation.
        """
        self._pending_refresh = True

        # If debounce task already scheduled and running, let it handle the refresh
        if self._debounce_task is not None and not self._debounce_task.done():
            logger.debug("Refresh already scheduled, coalescing request")
            return

        # Schedule debounced refresh
        self._debounce_task = asyncio.create_task(self._debounced_refresh())

    async def _debounced_refresh(self) -> None:
        """Execute refresh after debounce delay."""
        await asyncio.sleep(DEBOUNCE_DELAY_SECONDS)

        if not self._pending_refresh:
            return

        self._pending_refresh = False
        self._last_refresh_time = time.monotonic()

        if self._cache_service is not None:
            try:
                logger.debug("Executing debounced cache refresh")
                await self._cache_service.refresh_tasks(broadcast=True)
            except Exception as e:
                logger.warning(f"Debounced refresh failed: {e}")

    @staticmethod
    def sync_now() -> Dict[str, Any]:
        """Perform immediate synchronization.

        Returns:
            Sync result dictionary
        """
        try:
            from frago.session.sync import sync_all_projects

            result = sync_all_projects()

            return {
                "success": True,
                "synced": result.synced,
                "updated": result.updated,
                "skipped": result.skipped,
                "errors": result.errors,
            }
        except Exception as e:
            logger.error(f"Sync failed: {e}")
            return {
                "success": False,
                "error": str(e),
            }
