"""
Status controller module.

Manages user and organisation status checks, session persistence, and login
refresh logic for the Workstatus desktop agent.

Key classes:
    StatusController: Checks user status via the cross-platform API, handles
        re-authentication (code 1001), and persists user/org data to the
        Python SQLite database.

Thread-safety: Not thread-safe; create one instance per async task.
"""

import configparser
import asyncio
from ws_package.entities.user_entity import UserEntity
from ws_package.log.writer import LogWriter
import json, sys
from pathlib import Path
from ws_package.models.base import ActivityInterval, async_session, UserDetail, OrgConfigurations
from ws_package.thread_manager import stop_all_threads
from sqlalchemy import text
from sqlalchemy import select, desc
import datetime
from sqlalchemy import delete
from ws_package.configs import config
from ws_package.configs.security import Security_config
import hmac
import hashlib
from ws_package.services.api_request_service import APIRequest
from ws_package.utils.api_log_policy import api_name_from_url
from ws_package.utils.db_lock_manager import db_lock_manager
from ws_package.utils.tracker_status import set_tracker_status

logger = LogWriter.file(__name__)

class StatusController:
    def __init__(self):
        # Instance-specific state (converted from class-level)
        self.user_entity: UserEntity = None
        self.sd_token = None
        self.org_id = None
        self.user_id = None
        self.status = None
        # Removed individual db_write_lock - now using centralized db_lock_manager
        self.config = configparser.ConfigParser()
        self.config.read('config.ini')

    async def set_cred(self, user_entity: UserEntity):
        """Populate instance credentials from a UserEntity.

        Args:
            user_entity: Authenticated user entity, or None to clear credentials.
        """
        if user_entity is None:
            logger.error("set_cred called with None user_entity")
            self.user_entity = None
            self.sd_token = None
            self.org_id = None
            self.user_id = None
            return
        
        self.user_entity = user_entity
        self.sd_token = user_entity.sd_token
        self.org_id = user_entity.org_id
        self.user_id = user_entity.user_id

    async def deletedb(self):
        """
        Delete all user data from database.
        
        FIXED: Added timeouts to all database operations to prevent indefinite hangs.
        """
        # Database operation timeout constant
        DB_OPERATION_TIMEOUT = 5.0
        
        try:
            # Use centralized write lock for database write operations
            with db_lock_manager.write_lock():
                async with async_session() as session:
                    try:
                        # Disable foreign key constraints temporarily (SQLite-specific)
                        # Note: PRAGMA changes are session-specific in SQLite
                        await asyncio.wait_for(
                            session.execute(text("PRAGMA foreign_keys = OFF;")),
                            timeout=DB_OPERATION_TIMEOUT
                        )
                        await asyncio.wait_for(
                            session.commit(),
                            timeout=DB_OPERATION_TIMEOUT
                        )

                        # Delete all rows from Userdetail table
                        await asyncio.wait_for(
                            session.execute(delete(UserDetail)),
                            timeout=DB_OPERATION_TIMEOUT
                        )
                        
                        # Re-enable foreign key constraints
                        await asyncio.wait_for(
                            session.execute(text("PRAGMA foreign_keys = ON;")),
                            timeout=DB_OPERATION_TIMEOUT
                        )
                        
                        # Single commit for both operations
                        await asyncio.wait_for(
                            session.commit(),
                            timeout=DB_OPERATION_TIMEOUT
                        )

                        logger.info("Userdetail table cleared successfully.")
                    except asyncio.TimeoutError:
                        logger.error("Database operation timed out after %ss in deletedb", DB_OPERATION_TIMEOUT)
                        await session.rollback()
                    except Exception as e:
                        logger.error("Error while clearing Userdetail table: %s", e, exc_info=True)
                        await session.rollback()

        except Exception as e:
            logger.error("Error while clearing Userdetail table: %s", e, exc_info=True)

    async def update_user_status(self):
        """Query the cross-platform status endpoint for the current user.

        Returns:
            Parsed JSON response dict on HTTP 200, or None on error/non-200.
        """
        url = config('cross_platform_url', 'Application')
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.sd_token}",
            "OrgID": str(self.org_id),
            "UserID": str(self.user_id),
            "SDToken": self.sd_token,
        }
        try:
            api_request = APIRequest(url=url, headers=headers, timeout=10)
            api_request.get(payload={})  # Synchronous call
            response_data = api_request.data()
            if api_request.response and api_request.response.status_code == 200:
                return response_data
            else:
                status_code = api_request.response.status_code if api_request.response else 'No response'
                logger.warning(
                    "User status check returned non-200 (%s): HTTP %s",
                    api_name_from_url(url),
                    status_code,
                )
                return None

        except Exception as e:
            logger.error(
                "Failed to call cross-platform status API (%s): %s",
                api_name_from_url(url),
                e,
                exc_info=True,
            )
            return None

    async def user_status(self, recursion_depth: int = 0, max_recursion: int = 2):        
        """
        Check user status with recursion protection.
        
        Args:
            recursion_depth: Current recursion depth (internal use)
            max_recursion: Maximum allowed recursion depth to prevent infinite loops
        
        Returns:
            bool: True if user is active, False otherwise
        """
        # Prevent infinite recursion
        if recursion_depth >= max_recursion:
            logger.error("[STATUS] Maximum recursion depth (%s) reached in user_status(). Stopping to prevent infinite loop.", max_recursion)
            logger.error("[STATUS] This may indicate a persistent authentication issue. Setting tracker to 'stop'.")
            set_tracker_status('stop')
            self.status = False
            return False
        
        status_response = await self.update_user_status()
        
        if status_response:
            code = status_response.get("response", {}).get("code")
            status_data = status_response.get("response", {}).get("data", {})
            if code == "200": 
                if (
                    status_data.get("org_deactivate_status") == "active" and
                    status_data.get("org_hard_deleted") == "no" and
                    status_data.get("plan_status") == "active" and
                    status_data.get("user_soft_deleted") == "no" and
                    status_data.get("user_hard_deleted") == "no" and
                    status_data.get("user_profile_deactivated") == "no"
                ):
                    logger.info("User status > %s", status_data)
                    # All conditions met - set tracker to 'start' (same as WebSocket)
                    set_tracker_status('start')
                    self.status = True
                    return True
                else:
                    logger.info("User status > %s", code)
                    logger.info("User status > %s", status_data)
                    # User/org deactivated/deleted - set tracker to 'stop' (same as WebSocket)
                    set_tracker_status('stop')
                    self.status = False
                    return False
            elif code == "1001": 
                logger.info("Code 1001 from status check: %s (recursion depth: %s)", code, recursion_depth)
                login_status = await self.login_call()
                if login_status == False:
                    logger.warning("[STATUS] Login call failed after receiving code 1001. Setting tracker to 'stop'.")
                    set_tracker_status('stop')
                    self.status = False
                    return False
                else:
                    # Login succeeded, check status again with incremented recursion depth
                    logger.info("[STATUS] Login succeeded, rechecking status (recursion depth: %s)", recursion_depth + 1)
                    return await self.user_status(recursion_depth=recursion_depth + 1, max_recursion=max_recursion)
            else:
                logger.warning("Unexpected response code in user status: %s", code)
                # On unexpected code, keep current status (don't change tracker status)
                return self.status if hasattr(self, 'status') else True
        else:
            logger.warning("Failed to fetch user status — keeping current status")
            # On failure, keep current status (don't change tracker status)
            return self.status if hasattr(self, 'status') else True
    
    async def get_last_counter(self):
        # Use centralized read lock for database read operations
        # Database operation timeout constant
        DB_OPERATION_TIMEOUT = 5.0
        
        with db_lock_manager.read_lock():
            async with async_session() as session:
                query = select(ActivityInterval).order_by(ActivityInterval.counter.desc()).limit(1)
                try:
                    result = await asyncio.wait_for(
                        session.scalars(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    last_entry = result.first()
                    return last_entry.counter if last_entry else 0
                except asyncio.TimeoutError:
                    logger.error("Database query timed out after %ss in get_last_counter", DB_OPERATION_TIMEOUT)
                    return 0
                except Exception as e:
                    logger.error("Failed to get last counter from database: %s", e, exc_info=True)
                    return 0

    async def get_last_org(self):
        """
        Get the latest organization configuration.
        
        CRITICAL FIX: Returns OrgEntity instead of ORM object to prevent lazy-loading
        issues and ensure proper attribute access after session closes.
        """
        from ws_package.entities.org_entity import OrgEntity
        
        # Database operation timeout constant
        DB_OPERATION_TIMEOUT = 5.0
        
        # Use centralized read lock for database read operations
        try:
            with db_lock_manager.read_lock():
                async with async_session() as session:
                    query = select(OrgConfigurations).order_by(desc(OrgConfigurations.id)).limit(1)
                    try:
                        result = await asyncio.wait_for(
                            session.scalars(query),
                            timeout=DB_OPERATION_TIMEOUT
                        )
                        record = result.first()
                    except asyncio.TimeoutError:
                        logger.error("Database query timed out after %ss in get_last_org", DB_OPERATION_TIMEOUT)
                        return None
                    except Exception as e:
                        logger.error("Failed to query OrgConfigurations from database: %s", e, exc_info=True)
                        return None
                    
                    # CRITICAL FIX: Extract all attributes while session is still open
                    if record is None:
                        return None
                    
                    # Create OrgEntity with all values loaded
                    return OrgEntity(
                        org_id=record.org_id,
                        user_id=record.user_id,
                        screenshot_interval=record.screenshot_interval,
                        date=record.date,
                        appandurlstatus=record.appandurlstatus,
                        ssfrequencystatus=record.ssfrequencystatus,
                        screenshotblurstatus=record.screenshotblurstatus,
                        livescreenshotstatus=record.livescreenshotstatus,
                        idlenotes_status=record.idlenotes_status,
                        break_limit=record.break_limit,
                        break_status=record.break_status,
                        notes_char_limit=record.notes_char_limit,
                        idle_limit=record.idle_limit,
                        can_delete_scrnshots=record.can_delete_scrnshots,
                        restrict_timer_to_jobsite=record.restrict_timer_to_jobsite,
                        todos_files_upload_limit=record.todos_files_upload_limit,
                        org_timezone=record.org_timezone,
                        user_timezone=record.user_timezone,
                        user_timezone_gmt=record.user_timezone_gmt,
                        user_timezone_gmt_dst=record.user_timezone_gmt_dst,
                        user_timezone_gmt_shortcode=record.user_timezone_gmt_shortcode,
                        bulkInvite=record.bulkInvite,
                        timeInterval=record.timeInterval,
                        blur_intensity=record.blur_intensity,
                        img_quality=record.img_quality,
                        notification_status=record.notification_status,
                        weekly_limit=getattr(record, 'weekly_limit', None),
                        org_week_start_day=getattr(record, 'org_week_start_day', None),
                        worked_total_hours_week=getattr(record, 'worked_total_hours_week', None),
                        worked_total_hours_week_leaving_today=getattr(record, 'worked_total_hours_week_leaving_today', None),
                        daily_limit=getattr(record, 'daily_limit', None),
                        weekly_limit_hours=getattr(record, 'weekly_limit_hours', None),
                        worked_total_hours_today=getattr(record, 'worked_total_hours_today', None),
                        limit_action=getattr(record, 'limit_action', None),
                        timezone_preference=getattr(record, 'timezone_preference', 1),
                    )
        except Exception as e:
            logger.error("Failed to get last org from database: %s", e, exc_info=True)
            return None

    async def get_last_user(self):
        """
        Get the latest user from database.
        
        FIXED: Added timeout to prevent indefinite hangs.
        """
        # Database operation timeout constant
        DB_OPERATION_TIMEOUT = 5.0
        
        # Use centralized read lock for database read operations
        with db_lock_manager.read_lock():
            async with async_session() as session:
                query = select(UserDetail).order_by(desc(UserDetail.id)).limit(1)
                try:
                    result = await asyncio.wait_for(
                        session.execute(query),
                        timeout=DB_OPERATION_TIMEOUT
                    )
                    user_data = result.scalars().first()
                    
                    if user_data:
                        # Extract data while session is still open
                        return UserEntity(
                            username=user_data.username,
                            name=user_data.name,
                            timezone=user_data.timezone,
                            token=user_data.token,
                            org_id=user_data.org_id,
                            user_id=user_data.user_id,
                            sd_token=user_data.sd_token,
                            bucket=user_data.bucket,
                            timezone_preference=getattr(
                                user_data, "timezone_preference", 1
                            ),
                        )
                    else:
                        return None
                except asyncio.TimeoutError:
                    logger.error("Database query timed out after %ss in get_last_user", DB_OPERATION_TIMEOUT)
                    return None
                except Exception as e:
                    logger.error("Failed to get last user from database: %s", e, exc_info=True)
                    return None

    async def refresh(self):
        logger.info("Refreshing — stopping all threads...")
        stop_all_threads()

    def get_file_path(self):
        """Resolve the path to the user token file for the current platform.

        Returns:
            Path object pointing to user_token_file.txt, or None if not found.
        """
        if sys.platform == "darwin":
            db_dir = Path.home() / "Library/Application Support/Workstatus"
            db_dir.mkdir(parents=True, exist_ok=True)
            file_path = db_dir / "user_token_file.txt"
        elif getattr(sys, 'frozen', False):
            script_path = Path(sys.executable).resolve()
            target_directory = script_path.parents[0]
            file_path = target_directory / "user_token_file.txt"
        else:
            script_path = Path(__file__).resolve()
            target_directory = script_path.parents[5]
            file_path = target_directory / "user_token_file.txt"

        if file_path.exists():
            logger.debug("User token file found at: %s", file_path)
            return file_path
        else:
            logger.warning("User token file not found: %s", file_path)
            return None

    async def developer(self):
        """Read the user token from the token file on disk.

        Returns:
            Token string, or None if the file does not exist or cannot be read.
        """
        file_path = self.get_file_path()
        if file_path:
            try:
                with open(file_path, "r", encoding="utf-8") as file:
                    return file.read()
            except (OSError, IOError) as e:
                logger.error("Failed to read user token file at %s: %s", file_path, e, exc_info=True)
                return None
            
    def generate_workstatus_hmac_signature(self, user_token: str, shared_secret: str) -> str:
        """
        Generate the X-Workstatus-HMAC-SHA256 header value.
        """
        signature = hmac.new(
            key=shared_secret.encode('utf-8'),
            msg=user_token.encode('utf-8'),
            digestmod=hashlib.sha256
        ).hexdigest()
        return signature
    
    async def login_call(self):
        """Re-authenticate after API code 1001 (token expired).

        Visible builds refresh from Electron DB only.
        Silent builds use user_token_file.txt stealth_login.
        """
        from ws_package.utils.build_mode import is_silent_build

        if not is_silent_build():
            return await self._login_call_from_electron_db()

        return await self._login_call_from_user_token()

    async def _login_call_from_electron_db(self) -> bool:
        """Visible mode: refresh credentials from Electron SQLite (no user_token_file)."""
        try:
            from ws_package.controllers.user_controller import UserController

            logger.info("[Auth] Visible login refresh — re-reading Electron database")
            controller = UserController()
            user_entity = await controller.authenticate_async()
            if not user_entity:
                logger.warning("[Auth] Visible login refresh failed — no user in Electron DB")
                return False

            await self.set_cred(user_entity)
            logger.info("[Auth] Visible login refresh succeeded for %s", user_entity.username)
            return True
        except Exception as e:
            logger.error("Visible login refresh error: %s", e, exc_info=True)
            return False

    async def _login_call_from_user_token(self) -> bool:
        """Silent mode: stealth_login via user_token_file.txt."""
        try:
            user_token = await self.developer()
            url = config('application_login_url', 'Application')
            payload = {
                "user_token": user_token,
                "device_type": "linux",
                "app_version": "7.0"
            }
            headers = {
                'Content-Type': 'application/json',
                'apptype': 'py',
                "X-Workstatus-HMAC-SHA256": self.generate_workstatus_hmac_signature(user_token, Security_config("shared_secret", "Security"))
            }

            api_request = APIRequest(url=url, headers=headers, timeout=10)
            api_request.post(payload)  # Synchronous call
            result = api_request.data()

            if not api_request.response or api_request.response.status_code != 200:
                logger.warning(
                    "Login API call failed (%s): HTTP %s",
                    api_name_from_url(url),
                    api_request.response.status_code if api_request.response else 'no response',
                )
                return False

            # Extract 'data' from nested 'response'
            res = result.get("response", {})
            user_data = res.get("data", {})

            if not user_data:
                logger.warning("Login response contained no user data — login failed")
                return False
            if res.get("active_plan_id") is None:
                logger.warning("Login response has no active_plan_id — login failed")
                return False
            if res.get("code") != "200":
                logger.warning("Login response code is not 200 (got %s) — login failed", res.get("code"))
                return False
        
            user_entity = UserEntity(
                username=user_data.get("email"),
                name=user_data.get("name"),
                timezone=user_data.get("timezone"),
                token=user_data.get("token"),
                org_id=user_data.get("org_id"),
                user_id=user_data.get("id"),
                sd_token=user_data.get("token"),
                bucket=res.get("bucket"), # Default empty string if not present
                timezone_preference=(
                    0
                    if (
                        user_data.get("timezone_preference", res.get("timezone_preference"))
                        in (0, "0", False)
                    )
                    else 1
                ),
            )
            
            self.user_entity = user_entity
            self.sd_token = user_data.get("token")
            self.org_id = user_data.get("org_id")
            self.user_id = user_data.get("id")
            
            await self.deletedb()
            await self.save_user(user_entity)
            await self.refresh()
            return True
            
        except Exception as e:
            logger.error("Unexpected error during login call: %s", e, exc_info=True)
            return False
    
    async def save_user(self, user_entity: UserEntity):
        """
        Save user to database.
        
        FIXED: Added timeouts to commit and refresh operations to prevent indefinite hangs.
        """
        # Database operation timeout constant
        DB_OPERATION_TIMEOUT = 5.0
        
        try:
            # Use centralized write lock for database write operations
            with db_lock_manager.write_lock():
                async with async_session() as session:
                    user_detail = UserDetail(
                        username=user_entity.username,
                        name=user_entity.name,
                        user_id=user_entity.user_id,
                        timezone=user_entity.timezone,
                        token=user_entity.token,
                        org_id=user_entity.org_id,
                        sd_token=user_entity.sd_token,
                        bucket=user_entity.bucket,
                        timestamp=datetime.datetime.now()
                    )
                    session.add(user_detail)
                    try:
                        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(user_detail),  # Get ID and other DB-assigned fields
                                timeout=DB_OPERATION_TIMEOUT
                            )
                            logger.info("User saved with ID: %s", user_detail.id)
                        except asyncio.TimeoutError:
                            # Refresh timeout is non-critical - commit already succeeded
                            logger.warning(
                                f"Refresh timed out after {DB_OPERATION_TIMEOUT}s (non-critical). "
                                f"Commit succeeded, data is persisted."
                            )
                            if hasattr(user_detail, 'id'):
                                logger.info("User saved with ID: %s", user_detail.id)
                        except Exception as refresh_error:
                            # Refresh failure is non-critical - commit already succeeded
                            logger.warning(
                                f"Could not refresh UserDetail instance after commit (non-critical): {refresh_error}. "
                                f"Commit succeeded, data is persisted."
                            )
                            # Try to get ID from the object if available
                            if hasattr(user_detail, 'id'):
                                logger.info("User saved with ID: %s", user_detail.id)
                        
                        return user_detail
                    except asyncio.TimeoutError:
                        logger.error("Database commit timed out after %ss in save_user", DB_OPERATION_TIMEOUT)
                        await session.rollback()
                        return None
                    except Exception as commit_error:
                        logger.error("Database commit failed in save_user: %s", commit_error, exc_info=True)
                        await session.rollback()
                        raise

        except Exception as e:
            logger.error("Database error while saving user: %s", e, exc_info=True)
            return None
       