"""
User controller module.

Handles user authentication, credential resolution from the Electron SQLite
database, and persistence of user/org data to the Python database.

Key classes:
    UserController: Loads user from Electron DB, saves to Python DB, fetches org config.

Thread-safety: UserController is not shared across threads; create one instance per call site.
"""

# ws_package/controllers/user_controller.py

import asyncio
from typing import Optional
import configparser
from ws_package.services.user_service import UserService
from ws_package.entities.user_entity import UserEntity
from ws_package.utils.token import get_user_token
from ws_package.configs import config
from ws_package.configs.security import Security_config
from ws_package.log.writer import LogWriter
import platform, sys
from pathlib import Path
from ws_package.models.base import async_session, UserDetail
from sqlalchemy import select, desc
import os
import hmac
import sqlite3
import hashlib
from ws_package.utils.db_lock_manager import db_lock_manager

logger = LogWriter.file(__name__)

ELECTRON_DB_FILENAME = "workstatus-desktop.sqlite"


class UserController:
    def __init__(self):
        # Instance-specific state (converted from class-level)
        self.user_service = UserService()
        self.config = configparser.ConfigParser()
        self.config.read('config.ini')
        # Removed individual db_write_lock - now using centralized db_lock_manager

    async def get_last_user(self) -> UserEntity | None:
        """Fetch the most recently saved user from the Python database.

        Used as a fallback when the Electron database is unavailable.

        Returns:
            UserEntity populated from the last DB row, or None if no rows exist.
        """
        logger.info("Fetching last user in case of server down case")
        # 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)
                result = await session.execute(query)
                user_data = result.scalars().first()
                if user_data:
                    user_entity = 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:
                    user_entity = None
            return user_entity

    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 the file does not exist.
        """
        if getattr(sys, 'frozen', False) and (platform.system() == "Windows" or platform.system() == "Linux"):
            script_path = Path(sys.executable).resolve()
            target_directory = script_path.parents[0] 
            file_path = target_directory / "user_token_file.txt"
        elif getattr(sys, 'frozen', False) and  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"
        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
   

    def get_electron_db_path(self):
        """Get the path to the Electron app's SQLite database"""
        if platform.system() == "Darwin":
            db_path = Path.home() / "Library/Application Support/Workstatus" / ELECTRON_DB_FILENAME
        elif platform.system() == "Linux":
            db_path = Path.home() / "Workstatus" / ELECTRON_DB_FILENAME
        else:
            db_path = Path.home() / "AppData/Roaming/Workstatus" / ELECTRON_DB_FILENAME
        
        logger.debug("Electron DB path resolved: %s", db_path)
        return str(db_path)

    def get_user_from_electron_db(self):
        """Get user data from Electron app's SQLite database"""
        db_path = self.get_electron_db_path()
        
        if not os.path.exists(db_path):
            logger.error("Electron database not found at: %s", db_path)
            return None
        
        conn = None
        try:
            conn = sqlite3.connect(db_path, timeout=5)
            conn.row_factory = sqlite3.Row   # must be set on connection, not cursor
            cursor = conn.cursor()

            cursor.execute("""
                SELECT id, username, name, user_id, timezone, token, org_id, sd_token,
                       bucket, timezone_preference, timestamp
                FROM userdetail
                ORDER BY timestamp DESC
                LIMIT 1
            """)

            row = cursor.fetchone()
            if row:
                user_dict = dict(row)
                logger.info("Retrieved user from Electron DB: %s (ID: %s)", user_dict.get('name'), user_dict.get('user_id'))
                return user_dict
            else:
                logger.warning("No user data found in Electron database")
                return None

        except sqlite3.OperationalError as e:
            logger.error("SQLite error reading from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        except sqlite3.DatabaseError as e:
            logger.error("Database error reading from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        except Exception as e:
            logger.error("Unexpected error reading from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        finally:
            if conn:
                conn.close()
    
    def get_latest_org_id_from_organization_settings(self) -> Optional[int]:
        """Fetch the latest org_id from organization_settings table in Electron DB.

        Returns the org_id from the most recent record ordered by updated_at, or None if not found.
        """
        db_path = self.get_electron_db_path()
        if not os.path.exists(db_path):
            logger.error("Electron database not found at: %s", db_path)
            return None

        conn = None
        try:
            conn = sqlite3.connect(db_path, timeout=5)
            cursor = conn.cursor()

            try:
                cursor.execute("""
                    SELECT org_id
                    FROM organization_settings
                    ORDER BY updated_at DESC
                    LIMIT 1
                """)
            except sqlite3.OperationalError as inner_exc:
                logger.warning("Fallback to id ordering for organization_settings (updated_at column missing?): %s", inner_exc)
                cursor.execute("""
                    SELECT org_id
                    FROM organization_settings
                    ORDER BY id DESC
                    LIMIT 1
                """)

            row = cursor.fetchone()
            if row and row[0] is not None:
                try:
                    return int(row[0])
                except Exception:
                    return row[0]
            return None
        except sqlite3.OperationalError as e:
            logger.error("SQLite error reading organization_settings from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        except sqlite3.DatabaseError as e:
            logger.error("Database error reading organization_settings from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        except Exception as e:
            logger.error("Unexpected error reading organization_settings from Electron database at %s: %s", db_path, e, exc_info=True)
            return None
        finally:
            if conn:
                conn.close()

    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

    def get_os_type(self):
        """Return a short OS type string ('Mac', 'Linux', or 'Windows').

        Returns:
            str: One of 'Mac', 'Linux', or 'Windows'.
        """
        if platform.system() == "Darwin":
            return "Mac"
        return platform.system()

    async def authenticate_async(self) -> UserEntity | None:
        """Authenticate from Electron DB (async-safe — no nested asyncio.run)."""
        logger.info("Starting authentication from Electron database")

        response_data = self.get_user_from_electron_db()

        if response_data is None:
            logger.warning("No user data found in Electron database, fetching last user from Python DB")
            return await self.get_last_user()

        org_id_from_userdetail = response_data.get('org_id')
        latest_org_id = self.get_latest_org_id_from_organization_settings()
        final_org_id = org_id_from_userdetail if org_id_from_userdetail is not None else latest_org_id

        if final_org_id is None:
            logger.error(
                "Cannot create UserEntity: org_id is None (userdetail.org_id=%s, organization_settings.org_id=%s)",
                org_id_from_userdetail, latest_org_id,
            )
            logger.error("Falling back to last user from Python DB")
            return await self.get_last_user()

        logger.info(
            "Using org_id: %s (from userdetail: %s, from organization_settings: %s)",
            final_org_id, org_id_from_userdetail, latest_org_id,
        )

        token = response_data.get('token')
        sd_token = response_data.get('sd_token')
        if not token:
            logger.warning("Electron DB user has no token — authentication will likely fail")
        if not sd_token:
            logger.warning("Electron DB user has no sd_token — API calls will fail")

        user_entity = UserEntity(
            username=response_data.get('username'),
            name=response_data.get('name'),
            timezone=response_data.get('timezone'),
            token=token,
            org_id=final_org_id,
            user_id=response_data.get('user_id'),
            sd_token=sd_token,
            bucket=response_data.get('bucket'),
            timezone_preference=(
                0 if response_data.get('timezone_preference') in (0, '0', False) else 1
            ),
        )

        logger.info("Created UserEntity for user: %s (ID: %s)", user_entity.name, user_entity.user_id)
        logger.info(
            "UserEntity details: org_id=%s, timezone_preference=%s (0=checks ON, 1=checks OFF), %s",
            user_entity.org_id,
            user_entity.timezone_preference,
            "sd_token=<present>" if user_entity.sd_token else "sd_token=<missing>",
        )

        logger.info(
            "Updating/saving user %s to Python DB (upsert - will update if exists, insert if not)",
            user_entity.user_id,
        )
        saved_user_entity = await self.__save_user(user_entity)

        if saved_user_entity:
            logger.info("User %s saved/updated in Python DB: org_id=%s", user_entity.user_id, saved_user_entity.org_id)
            return saved_user_entity

        logger.warning("Failed to save user %s to Python DB, returning entity from Electron DB", user_entity.user_id)
        return user_entity

    def authenticate(self) -> UserEntity | None:
        """Sync wrapper — use ``authenticate_async`` inside a running event loop."""
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            return asyncio.run(self.authenticate_async())
        raise RuntimeError(
            "UserController.authenticate() cannot be called from a running event loop; "
            "use await authenticate_async() instead."
        )
    
    async def __save_user(self, user_entity: UserEntity):
        return await self.user_service.save_user(user_entity)
    


