import logging, sys, os
from logging import handlers
from pathlib import Path
import unicodedata
import threading
import platform
from .thread_logger import get_thread_logger, ThreadLogManager

# Log level mapping
_LOG_LEVEL_MAP = {
    'DEBUG': logging.DEBUG,
    'INFO': logging.INFO,
    'WARNING': logging.WARNING,
    'ERROR': logging.ERROR,
    'CRITICAL': logging.CRITICAL,
}

# Constants for log file management
MAX_LOG_FILE_SIZE_MB = 10  # Maximum log file size in MB before rotation
LOG_FILE_BACKUP_COUNT = 10  # Keep more history to preserve initial logs
BYTES_PER_MB = 1024 * 1024  # Bytes per megabyte

# Windows-safe RotatingFileHandler that handles file locking gracefully
class WindowsSafeRotatingFileHandler(handlers.RotatingFileHandler):
    """
    RotatingFileHandler that gracefully handles Windows file locking issues.
    On Windows, when multiple processes/handlers try to rotate the same file,
    PermissionError can occur. This handler catches and suppresses those errors.
    Windows-only: Only used on Windows platform.
    """
    def doRollover(self):
        """
        Override doRollover to handle Windows file locking gracefully.
        If rotation fails due to file locking, we silently continue.
        Logs will still be written, just rotation will be skipped this time.
        """
        try:
            super().doRollover()
        except (PermissionError, OSError) as e:
            # On Windows, file locking can prevent rotation
            # This is not critical - logs will still be written
            # Silently continue (don't log the error to avoid recursion)
            if platform.system() == 'Windows':
                # On Windows, this is expected behavior when multiple handlers share the file
                pass
            else:
                # On other platforms, re-raise the error
                raise

def get_configured_log_level(module_name=None, default_level=logging.INFO):
    """Return log level from WORKSTATUS_LOG_LEVEL env var, defaulting to INFO."""
    env_level = os.environ.get("WORKSTATUS_LOG_LEVEL", "").upper()
    return _LOG_LEVEL_MAP.get(env_level, default_level)

def set_log_level(logger_instance, level):
    """
    Set log level for a logger instance programmatically.

    Args:
        logger_instance: Logger instance (LogWriter or standard logging.Logger)
        level: Log level (string like 'DEBUG', 'INFO', etc. or logging constant)

    Returns:
        bool: True if level was set successfully, False otherwise

    Example:
        >>> logger = LogWriter.file(__name__)
        >>> set_log_level(logger, 'DEBUG')
        >>> set_log_level(logger, logging.INFO)
    """
    try:
        # Convert string level to logging constant if needed
        if isinstance(level, str):
            level = level.upper()
            if level not in _LOG_LEVEL_MAP:
                return False
            level = _LOG_LEVEL_MAP[level]

        # Set level on logger instance
        if hasattr(logger_instance, 'setLevel'):
            logger_instance.setLevel(level)

            # Also set level on all handlers
            if hasattr(logger_instance, 'handlers'):
                for handler in logger_instance.handlers:
                    handler.setLevel(level)

            return True
    except Exception:
        return False

def safe_unicode_string(text):
    """
    Safely handle Unicode strings for logging by normalizing and replacing problematic characters.
    
    Args:
        text: Input string that may contain Unicode characters
        
    Returns:
        str: Safe string for logging
    """
    if not isinstance(text, str):
        text = str(text)
    
    # Normalize Unicode characters
    text = unicodedata.normalize('NFKD', text)
    
    # Replace problematic Unicode characters with ASCII equivalents
    replacements = {
        '\u2713': '[SUCCESS]',  #  checkmark
        '\u2717': '[FAIL]',     # ✗ cross mark
        '\u200b': '',           # zero-width space
        '\u200c': '',           # zero-width non-joiner
        '\u200d': '',           # zero-width joiner
        '\ufeff': '',           # zero-width no-break space
    }
    
    for unicode_char, replacement in replacements.items():
        text = text.replace(unicode_char, replacement)
    
    return text

def get_log_directory():
    """Get the appropriate log directory based on platform and execution context"""
    if sys.platform == "darwin":
        log_dir = Path.home() / "Library/Application Support/Workstatus/logs"
    elif sys.platform == "win32":
        # For Windows, use AppData or current directory if AppData is not accessible
        if getattr(sys, 'frozen', False):
            # Running as frozen executable
            try:
                appdata_dir = Path(os.environ.get('APPDATA', '')) / "Workstatus"
                appdata_dir.mkdir(parents=True, exist_ok=True)
                log_dir = appdata_dir / "logs"
            except (PermissionError, OSError):
                # Fallback to executable directory
                log_dir = Path(sys.executable).parent / "logs"
        else:
            # Running in development
            log_dir = Path.cwd() / "logs"
    else:
        # Linux and other platforms
        log_dir = Path.home() / "Workstatus" / "logs"
    
    # Ensure directory exists with fallback mechanism
    try:
        log_dir.mkdir(parents=True, exist_ok=True)
    except (PermissionError, OSError) as e:
        # If creation fails (e.g. permissions), fallback to a safe location
        try:
            if sys.platform == "darwin":
                # Fallback 1: User's home directory directly
                log_dir = Path.home() / "Workstatus" / "logs"
                log_dir.mkdir(parents=True, exist_ok=True)
            else:
                raise
        except (PermissionError, OSError):
            # Fallback 2: Temp directory
            import tempfile
            log_dir = Path(tempfile.gettempdir()) / "Workstatus" / "logs"
            log_dir.mkdir(parents=True, exist_ok=True)
            
    # Use built-in logging to avoid circular dependency with LogWriter
    # This logs to stderr/console since file handlers aren't set up yet
    init_logger = logging.getLogger('workstatus.init')
    if not init_logger.handlers:
        # Only add handler if none exists to avoid duplicates
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
        init_logger.addHandler(handler)
        init_logger.setLevel(logging.INFO)
    init_logger.info("Log directory: %s", log_dir)
    return log_dir

log_dir = get_log_directory()
log_txt_path = log_dir / "logs.txt"
class SafeUnicodeFormatter(logging.Formatter):
    """
    Enhanced formatter that safely handles Unicode characters and supports structured logging.
    Includes module name, thread name, and extra context in log messages.
    """
    
    def __init__(self, fmt=None, datefmt=None, include_thread=True, include_extra=True):
        """
        Initialize the formatter.
        
        Args:
            fmt: Format string (default includes thread name if include_thread=True)
            datefmt: Date format string
            include_thread: Whether to include thread name in format
            include_extra: Whether to include extra context data
        """
        # Default format with thread name support
        if fmt is None:
            if include_thread:
                fmt = '%(asctime)s - %(name)s - [%(threadName)s] - %(levelname)s - %(message)s'
            else:
                fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        
        super().__init__(fmt, datefmt)
        self.include_thread = include_thread
        self.include_extra = include_extra
    
    def format(self, record):
        """
        Format the log record with Unicode safety and extra context support.
        
        Args:
            record: LogRecord instance
            
        Returns:
            Formatted log message string
        """
        # Ensure thread name is set if not present or empty
        # Note: threadName is always a built-in attribute, so we check if it's set to a meaningful value
        if self.include_thread:
            # Check if threadName is missing, empty, or a generic Thread-N name
            current_thread_name = threading.current_thread().name
            if (not hasattr(record, 'threadName') or 
                not record.threadName or 
                record.threadName.startswith('Thread-') or
                record.threadName == 'Thread-1'):
                # Only set if filter hasn't already set it from extra_data
                if not hasattr(record, 'extra_data') or not isinstance(record.extra_data, dict) or 'thread_name' not in record.extra_data:
                    # Use current thread name if it's meaningful, otherwise use a default based on module
                    if current_thread_name and not current_thread_name.startswith('Thread-'):
                        record.threadName = current_thread_name
                    else:
                        # Extract meaningful name from logger name (e.g., "activity_controller" from "ws_package.controllers.activity_controller")
                        logger_name = getattr(record, 'name', '')
                        if logger_name:
                            parts = logger_name.split('.')
                            record.threadName = parts[-1] if parts else 'MainThread'
                        else:
                            record.threadName = 'MainThread'
        
        # Handle extra context data
        if self.include_extra:
            # Check for extra_data in record (from thread logger or log_with_context)
            extra_data = None
            if hasattr(record, 'extra_data') and record.extra_data:
                extra_data = record.extra_data
            # Also check standard logging 'extra' dict for extra_data
            elif hasattr(record, '__dict__') and 'extra_data' in record.__dict__:
                extra_data = record.__dict__['extra_data']
            
            if extra_data:
                context_parts = []
                for key, value in extra_data.items():
                    # Skip traceback as it's usually included in message
                    if key != 'traceback':
                        # Format value safely
                        if isinstance(value, (dict, list)):
                            try:
                                import json
                                value = json.dumps(value, default=str, ensure_ascii=False)
                            except Exception:
                                value = str(value)
                        context_parts.append(f"{key}={value}")
                
                if context_parts:
                    # Append context to message
                    original_msg = record.getMessage()
                    context_str = " | " + " | ".join(context_parts)
                    record.msg = original_msg + context_str
                    record.args = ()  # Clear args since we've formatted the message
        
        # Get the formatted message
        message = super().format(record)
        
        # Apply safe Unicode handling
        return safe_unicode_string(message)

class LogWriter(logging.Logger):
    def __init__(self, name, level=None, h: str = 'file'):
        # If level is None or 0, use configured log level
        if level is None or level == 0:
            level = get_configured_log_level()
        super().__init__(name, level)
        # Initialize cached handler as None
        self._file_handler = None
        if not self.hasHandlers():
            self.addHandler(self.file_handler)
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(self.formatter)
            # Set UTF-8 encoding for console handler to handle Unicode characters
            if hasattr(console_handler.stream, 'reconfigure'):
                try:
                    console_handler.stream.reconfigure(encoding='utf-8', errors='replace')
                except Exception:
                    # Fallback if reconfigure fails
                    pass
            self.addHandler(console_handler)

    @property
    def file_handler(self):
        # Cache handler instance to prevent duplicate handlers
        if self._file_handler is None:
            # Use Windows-friendly handler that handles file locking gracefully (Windows only)
            if platform.system() == 'Windows':
                self._file_handler = WindowsSafeRotatingFileHandler(
                    str(log_txt_path), 
                    maxBytes=MAX_LOG_FILE_SIZE_MB * BYTES_PER_MB, 
                    backupCount=LOG_FILE_BACKUP_COUNT, 
                    encoding='utf-8'
                )
            else:
                # Linux/macOS: Use standard RotatingFileHandler
                self._file_handler = handlers.RotatingFileHandler(
                    str(log_txt_path), 
                    maxBytes=MAX_LOG_FILE_SIZE_MB * BYTES_PER_MB, 
                    backupCount=LOG_FILE_BACKUP_COUNT, 
                    encoding='utf-8'
                )
            self._file_handler.setFormatter(self.formatter)
        return self._file_handler

    @property
    def formatter(self):
        # Enhanced formatter with thread name for better traceability (simplified, no module path)
        return SafeUnicodeFormatter(
            '%(asctime)s - [%(threadName)s] - %(levelname)s - %(message)s',
            include_thread=True,
            include_extra=True
        )

    @classmethod
    def json(cls, name, level=None):
        """Redirect: json logging is disabled; returns a standard file logger."""
        return cls(name, level, 'file')

    @classmethod
    def file(cls, name, level=None):
        return cls(name, level, 'file')

    def processNo(self, filename: str, message: str, level=logging.INFO):
        """Writes logs to a separate file without interfering with the main logger."""
        # Ensure filename is in a writable directory
        file_path = Path(filename)
        if not file_path.is_absolute():
            file_path = log_dir / file_path.name
        
        handler = logging.FileHandler(str(file_path), encoding='utf-8')
        handler.setFormatter(self.formatter)
        
        temp_logger = logging.getLogger(f"{self.name}_separate")
        temp_logger.setLevel(logging.INFO)  # Ensure INFO level logs are written
        temp_logger.addHandler(handler)
        
        temp_logger.info(message)  # Write the log message
        
        # Remove handler after writing to avoid duplicate logs
        temp_logger.removeHandler(handler)
        handler.close()
    
    def log_with_context(self, level, message, **context):
        """
        Log a message with additional context data.
        
        Args:
            level: Log level (logging.DEBUG, logging.INFO, etc.)
            message: Log message
            **context: Additional context key-value pairs to include in log
            
        Example:
            >>> logger.log_with_context(logging.INFO, "User action", user_id=123, action="login")
        """
        extra = {'extra_data': context} if context else {}
        self.log(level, message, extra=extra)

# Global thread logger manager instance
_thread_logger_manager = None

def initialize_thread_logging():
    """Initialize the thread-based logging system"""
    global _thread_logger_manager
    if _thread_logger_manager is None:
        _thread_logger_manager = ThreadLogManager.get_instance()
    return _thread_logger_manager

def get_logging_stats():
    """Get logging statistics"""
    if _thread_logger_manager:
        return _thread_logger_manager.get_stats()
    return {}

def stop_thread_logging():
    """Stop the thread-based logging system"""
    global _thread_logger_manager
    if _thread_logger_manager:
        _thread_logger_manager.stop()
        _thread_logger_manager = None

def enable_log_upload(user_entity, org_id: str, s3_logs_path: str = "logs/"):
    """Enable automatic log upload to server"""
    from .thread_logger import enable_log_upload
    enable_log_upload(user_entity, org_id, s3_logs_path)

def enable_hourly_log_upload(user_entity, org_id: str, s3_logs_path: str = "logs/"):
    """Enable hourly log upload through the logging thread"""
    from .thread_logger import enable_hourly_log_upload
    enable_hourly_log_upload(user_entity, org_id, s3_logs_path)

def disable_log_upload():
    """Disable automatic log upload to server"""
    from .thread_logger import disable_log_upload
    disable_log_upload()

def disable_hourly_log_upload():
    """Disable hourly log upload"""
    from .thread_logger import disable_hourly_log_upload
    disable_hourly_log_upload()

def force_log_upload():
    """Force an immediate log upload"""
    from .thread_logger import force_log_upload
    force_log_upload()

def force_hourly_log_upload():
    """Force an immediate hourly log upload"""
    from .thread_logger import force_hourly_log_upload
    force_hourly_log_upload()