"""
services.mode_service — stealth / fixed-slot mode persistence and API sync.

``FixedService`` owns two concerns:

* **Local persistence** — read/write the ``FixedMode`` SQLite table that stores
  the active stealth-mode schedule (``save_fixed_mode``, ``getting_old_data``).

* **Remote refresh** — fetch the current stealth-mode configuration from the
  API server (``get_fixed_slots`` / ``fetch_fixed_mode``).

Async-safety note
-----------------
``fetch_fixed_mode`` is a synchronous ``requests`` call.  It is always invoked
through the async wrapper ``get_fixed_slots``, which uses
``asyncio.get_event_loop().run_in_executor`` to offload it to the default
thread-pool.  **Never** call ``fetch_fixed_mode`` directly from an async
context — doing so blocks the event loop for the full timeout + retry duration
(up to ~127 s on a degraded network).
"""
import asyncio
from ws_package.entities.user_entity import UserEntity
from ws_package.services.api_request_service import APIRequest
from ws_package.configs import config
from typing import Optional
from ws_package.entities.mode_entity import ModeEntity
from ws_package.models.base import async_session
from ws_package.models.base import FixedMode
from ws_package.log.writer import LogWriter
from sqlalchemy import select, desc
from ws_package.utils.db_lock_manager import db_lock_manager
logger = LogWriter.file(__name__)

# Database operation timeout (5 seconds)
DB_OPERATION_TIMEOUT = 5.0

class FixedService:
    def __init__(self):
        # Instance-specific state (converted from class-level)
        self.stealth_setting_url: Optional[str] = None
        self.headers: Optional[dict] = None
        # Removed individual db_write_lock - now using centralized db_lock_manager
    async def save_fixed_mode(self, mode_entity: ModeEntity):
        try:
            # Use centralized write lock for database write operations
            with db_lock_manager.write_lock():
                async with async_session() as session:
                    fixed_mode_data = FixedMode(
                        **mode_entity.model_dump()
                    )
                    session.add(fixed_mode_data)
                    await asyncio.wait_for(session.commit(), timeout=DB_OPERATION_TIMEOUT)
                    
                    # Refresh is optional - if it fails, the commit already succeeded
                    try:
                        await asyncio.wait_for(session.refresh(fixed_mode_data), timeout=DB_OPERATION_TIMEOUT)
                    except Exception as refresh_error:
                        # Refresh failure is non-critical - commit already succeeded
                        logger.warning(
                            f"Could not refresh FixedMode instance after commit (non-critical): {refresh_error}. "
                            f"Commit succeeded, data is persisted."
                        )
                    
                    return fixed_mode_data     
        except asyncio.TimeoutError:
            logger.error("Database operation timed out after %ss while saving fixed mode", DB_OPERATION_TIMEOUT)
            return False
        except Exception as e:  
            logger.exception("Error: %s. Failed to save Fixed Mode to DB.", e) 
            return False

    async def getting_old_data(self) -> ModeEntity:
        """
        Get the latest fixed mode data.
        
        CRITICAL FIX: Extracts all attributes while session is still open and returns
        ModeEntity instead of SQLAlchemy ORM object. This prevents lazy-loading
        issues after the session closes and ensures the read lock releases immediately.
        """
        # Use centralized read lock for database read operations
        try:
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    result = await asyncio.wait_for(
                        session.scalars(
                            select(FixedMode).order_by(FixedMode.id.desc())
                        ),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    record = result.first()
                    
                    # CRITICAL FIX: Extract all attributes while session is still open
                    # This prevents lazy-loading issues after the session closes
                    if record is None:
                        return None
                    
                    # Create ModeEntity with all values loaded
                    # This ensures no lazy loading happens after session closes
                    return ModeEntity(
                        limit_start=record.limit_start,
                        limit_end=record.limit_end,
                        day_list=record.day_list,
                        mode=record.mode,
                        tracking=record.tracking
                    )
        except asyncio.TimeoutError:
            logger.error("Database read operation timed out after %ss while getting old data", DB_OPERATION_TIMEOUT)
            return None
        except Exception as e:
            logger.error("Error getting old data: %s", e)
            return None

    async def get_fixed_slots(self, user_entity: UserEntity):
        """Fetch stealth-mode (fixed-slot) configuration from the remote API.

        The underlying HTTP call is synchronous (``requests``).  We offload it
        to the default thread-pool executor so the asyncio event loop is never
        blocked, regardless of network latency or retry duration.

        Returns the raw response dict on success, or ``None`` on failure.
        """
        self.stealth_setting_url = config('stealth_mode_url', 'Application')
        sd_token = user_entity.sd_token
        org_id = user_entity.org_id
        user_id = user_entity.user_id
        bucket = user_entity.bucket
        self.headers = {
            'SDToken': str(sd_token),
            'OrgID': str(org_id),
            'UserID': str(user_id),
            'bucket': str(bucket),
            'Authorization': f'Bearer {sd_token}',
        }
        payload = {"org_id": org_id}

        # Run the blocking HTTP call in a thread-pool executor so the event
        # loop stays free during network I/O and any retry back-off sleeps.
        loop = asyncio.get_event_loop()
        response_data = await loop.run_in_executor(None, self.fetch_fixed_mode, payload)
        return response_data  # None on failure, dict on success

    def fetch_fixed_mode(self, payload):
        """Synchronous HTTP GET for stealth-mode configuration.

        Intentionally synchronous — always call via ``get_fixed_slots()``
        (which wraps it in ``run_in_executor``) rather than awaiting directly,
        to avoid blocking the event loop.
        """
        if self.stealth_setting_url is not None and self.headers is not None:
            self.api_request = APIRequest(
                url=self.stealth_setting_url,
                headers=self.headers,
            )
            self.api_request.get(payload)
            return self.api_request.data()
        return None
        