# SPIDER_LOG v2.2 (OSAF_LOGS v2.1) - ИСПРАВЛЕННАЯ ВЕРСИЯ
import inspect
import os
import sys
import threading
import time
import traceback
from datetime import datetime
from enum import Enum
from typing import Dict, List, Optional, Union


class LogLevel(Enum):
    DEEP_DEBUG = 'deep-debug'
    DEBUG = 'debug'
    INFO = 'info'
    WARNING = 'warn'
    ERROR = 'error'
    FATAL = 'fatal'


# Общие маппинги для всех обработчиков
LEVEL_SHORT_NAMES = {
    'info': 'i',
    'info_hi': 'ih',
    'warn': 'w',
    'error': 'E',
    'debug': 'D',
    'fatal': 'F',
    'deep-debug': 'DD'
}

LEVEL_COLORS = {
    'info': '\033[97m',  # WHITE
    'info_hi': '\033[92m\033[1m',  # GREEN + BOLD
    'warn': '\033[93m\033[1m',  # YELLOW + BOLD
    'error': '\033[91m\033[1m',  # RED + BOLD
    'debug': '\033[96m',  # CYAN
    'fatal': '\033[95m\033[1m',  # MAGENTA + BOLD
    'deep-debug': '\033[90m'  # GRAY
}

COLOR_END = '\033[0m'

# ИСПРАВЛЕННАЯ конфигурация по умолчанию
DEFAULT_CONFIG = {
    'core': {
        'log_dir': 'logs',
        'buffer_size': 8192,
        'source_path_depth': 2
    },
    'threads': [
        {
            'name': 'main-console',
            'time_format': '%H:%M:%S',
            'out': 'stdout',
            'levels': ['info', 'info_hi', 'warn', 'error', 'fatal'],
            'use_colors': True,
            'format': '[{time}] [{source}] [{level}] {message}'
        },
        {
            'name': 'main-file',
            'time_format': '%Y-%m-%d %H:%M:%S',
            'out': 'main.log',
            'levels': ['info', 'info_hi', 'warn', 'error', 'fatal'],
            'format': '[{time}] [{source}] [{level}] {message}'
        },
        {
            'name': 'main-file',
            'time_format': '%Y-%m-%d %H:%M:%S',
            'out': 'debug.log',
            'levels': ['info', 'info_hi', 'warn', 'error', 'debug', 'fatal', 'deep-debug'],
            'format': '[{time}] [{source}] [{level}] {message}'
        }
    ]
}


class LogMessage:
    """Сообщение лога с улучшенной структурой"""
    __slots__ = ('timestamp', 'msg', 'level', 'source', 'pid', 'thread_id')

    def __init__(self, msg: str, level: str, source: str):
        self.timestamp = datetime.now()
        self.msg = str(msg)
        self.level = level
        self.source = source
        self.pid = os.getpid()
        self.thread_id = threading.current_thread().ident


class BaseHandler:
    """Базовый класс для обработчиков логов"""

    def __init__(self, config: dict):
        self.config = config
        self._lock = threading.RLock()
        self._closed = False
        self.name = config.get('name', 'unknown')
        self.levels = set(config.get('levels', []))
        self.time_format = config.get('time_format', '%H:%M:%S')
        self.format_str = config.get('format', '[{time}] [{source}] [{level}] {message}')

    def should_handle(self, level: str) -> bool:
        """Проверяет, должен ли обработчик обрабатывать данный уровень"""
        return not self.levels or level in self.levels

    def format_message(self, message: LogMessage) -> str:
        """Форматирует сообщение для вывода"""
        tm = message.timestamp.strftime(self.time_format)
        short_level = LEVEL_SHORT_NAMES.get(message.level, message.level.upper())

        return self.format_str.format(
            time=tm,
            source=message.source,
            level=short_level,
            message=message.msg
        )

    def handle(self, message: LogMessage) -> None:
        """Обработать сообщение"""
        if self._closed or not self.should_handle(message.level):
            return
        self._handle_impl(message)

    def _handle_impl(self, message: LogMessage) -> None:
        """Реализация обработки сообщения"""
        raise NotImplementedError()

    def close(self) -> None:
        """Закрыть обработчик"""
        with self._lock:
            self._closed = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()


class ConsoleHandler(BaseHandler):
    """Обработчик для вывода в консоль"""

    def __init__(self, config: dict):
        super().__init__(config)
        self.use_colors = config.get('use_colors', True)

    def _handle_impl(self, message: LogMessage) -> None:
        """Реализация вывода в консоль"""
        try:
            formatted_msg = self.format_message(message)

            if self.use_colors and sys.stdout.isatty():
                color = LEVEL_COLORS.get(message.level, COLOR_END)
                formatted_msg = f"{color}{formatted_msg}{COLOR_END}"

            # Прямой вывод в stdout с flush для немедленного отображения
            print(formatted_msg, flush=True)

        except Exception as e:
            # Фолбэк на случай ошибок форматирования
            print(f"Logger console error: {e}", flush=True)


class FileHandler(BaseHandler):
    """Обработчик для записи в файл"""

    def __init__(self, config: dict, log_dir: str):
        super().__init__(config)
        self.filename = config['out']
        self.log_dir = log_dir

        # Настройки буферизации
        self.buffer_size = config.get('buffer_size', 8192)
        self._buffer = []
        self._last_flush = time.time()
        self._flush_interval = 0.5  # секунды

        self._file_handle = None
        self._ensure_file()

    def _ensure_file(self) -> None:
        """Обеспечить существование файла и директории"""
        try:
            file_path = os.path.join(self.log_dir, self.filename)
            os.makedirs(os.path.dirname(file_path), exist_ok=True)

            if self._file_handle is None or self._file_handle.closed:
                self._file_handle = open(file_path, 'a', encoding='utf-8', buffering=1)
                # print(f"File handler opened: {file_path}", flush=True)

        except Exception as e:
            print(f"File handler error: {e}", flush=True)
            self._file_handle = None

    def _handle_impl(self, message: LogMessage) -> None:
        """Реализация записи в файл"""
        try:
            if self._file_handle is None:
                self._ensure_file()
                if self._file_handle is None:
                    return

            formatted_msg = self.format_message(message)

            # Буферизованная запись
            self._buffer.append(formatted_msg + '\n')
            self._auto_flush()

        except Exception as e:
            print(f"File write error: {e}", flush=True)
            self._file_handle = None

    def _auto_flush(self) -> None:
        """Автоматическая запись буфера по таймеру или размеру"""
        current_time = time.time()
        if (len(self._buffer) >= 10 or
                (current_time - self._last_flush) >= self._flush_interval):
            self._flush()

    def _flush(self) -> None:
        """Принудительная запись буфера"""
        if not self._buffer or self._file_handle is None:
            return

        try:
            self._file_handle.writelines(self._buffer)
            self._file_handle.flush()
            self._buffer.clear()
            self._last_flush = time.time()
        except Exception as e:
            print(f"Flush error: {e}", flush=True)
            self._file_handle = None

    def close(self) -> None:
        """Закрытие с гарантированной записью буфера"""
        with self._lock:
            if not self._closed:
                self._flush()
                if self._file_handle and not self._file_handle.closed:
                    self._file_handle.close()
                    # print(f"File handler closed: {self.filename}", flush=True)
                super().close()


class SpiderLog:
    """
    Улучшенный логгер с поддержкой многопоточности и буферизацией
    """

    _instance = None
    _lock = threading.Lock()

    def __init__(self, config: Optional[dict] = None):
        if config is None:
            config = DEFAULT_CONFIG.copy()

        self.config = config
        self._handlers: List[BaseHandler] = []
        self._source_cache: Dict[int, str] = {}
        self._lock = threading.RLock()
        self._closed = False

        # Настройки из конфига
        core_config = config.get('core', {})
        self.log_dir = core_config.get('log_dir', 'logs')
        self.source_path_depth = core_config.get('source_path_depth', 2)

        # Создаем директорию для логов
        os.makedirs(self.log_dir, exist_ok=True)

        # Инициализация обработчиков
        self._init_handlers(config.get('threads', []))

        # Регистрируем обработчик для необработанных исключений
        sys.excepthook = self._global_exception_handler

        # Выводим сообщение о успешной инициализации
        # self.i("Logger initialized successfully")

    def _init_handlers(self, threads_config: List[dict]) -> None:
        """Инициализация обработчиков на основе конфигурации"""
        if not threads_config:
            # Создаем обработчик по умолчанию если нет конфигурации
            threads_config = [{
                'name': 'default-console',
                'time_format': '%H:%M:%S',
                'out': 'stdout',
                'levels': ['info', 'info_hi', 'warn', 'error', 'fatal', 'debug', 'deep-debug'],
                'use_colors': True
            }]

        for thread_config in threads_config:
            try:
                out_target = thread_config.get('out', 'stdout')

                if out_target == 'stdout' or out_target == 'stderr':
                    handler = ConsoleHandler(thread_config)
                else:
                    handler = FileHandler(thread_config, self.log_dir)

                self._handlers.append(handler)
                # print(f"Initialized handler: {handler.name} for levels: {thread_config.get('levels', 'ALL')}",
                #       flush=True)

            except Exception as e:
                print(f"Failed to init handler {thread_config.get('name', 'unknown')}: {e}", flush=True)

    def _get_source(self, frame_depth: int = 3) -> str:
        """
        Определяет источник лога с кэшированием
        """
        try:
            # Получаем текущий фрейм
            frame = inspect.currentframe()
            for _ in range(frame_depth):
                if frame is None:
                    return "unknown"
                frame = frame.f_back

            if frame is None:
                return "unknown"

            cache_key = id(frame)
            if cache_key in self._source_cache:
                return self._source_cache[cache_key]

            module = inspect.getmodule(frame)
            if module and hasattr(module, '__file__'):
                filepath = module.__file__
                # Нормализуем путь
                filepath = os.path.normpath(filepath)

                # Получаем относительный путь от текущей рабочей директории
                try:
                    cwd = os.getcwd()
                    if filepath.startswith(cwd):
                        filepath = os.path.relpath(filepath, cwd)
                except ValueError:
                    # На случай, если пути на разных дисках (Windows)
                    pass

                # Ограничиваем глубину пути
                parts = filepath.split(os.sep)
                if len(parts) > self.source_path_depth:
                    parts = parts[-self.source_path_depth:]
                source = os.sep.join(parts)
            else:
                source = os.path.basename(frame.f_code.co_filename)

            # Кэшируем результат
            self._source_cache[cache_key] = source

            # Очистка кэша при превышении размера
            if len(self._source_cache) > 1000:
                # Оставляем только последние 500 записей
                keys_to_keep = list(self._source_cache.keys())[-500:]
                new_cache = {}
                for key in keys_to_keep:
                    new_cache[key] = self._source_cache[key]
                self._source_cache = new_cache

            return source

        except Exception:
            return "unknown"

    def _log(self, level: str, msg: str, is_fatal: bool = False) -> None:
        """Основной метод логирования"""
        if self._closed:
            return

        try:
            source = self._get_source()
            log_message = LogMessage(msg, level, source)

            # Передаем сообщение всем обработчикам
            for handler in self._handlers:
                try:
                    handler.handle(log_message)
                except Exception as e:
                    print(f"Handler {handler.name} error: {e}", flush=True)

            if is_fatal:
                self._log_fatal_error(msg)

        except Exception as e:
            print(f"Logging error: {e}", flush=True)

    def _log_fatal_error(self, msg: str) -> None:
        """Обработка фатальных ошибок"""
        try:
            error_msg = f"FATAL ERROR: {msg}\n{traceback.format_exc()}"
            # Создаем сообщение об ошибке с принудительной обработкой
            source = self._get_source()
            emergency_msg = LogMessage(error_msg, 'fatal', source)

            # Принудительно выводим фатальную ошибку во все обработчики
            for handler in self._handlers:
                try:
                    # Для фатальных ошибок пропускаем фильтрацию по уровням
                    formatted_msg = handler.format_message(emergency_msg) + '\n'
                    if isinstance(handler, ConsoleHandler):
                        color = LEVEL_COLORS.get('fatal', COLOR_END)
                        formatted_msg = f"{color}{formatted_msg}{COLOR_END}"
                        print(formatted_msg, flush=True)
                    elif isinstance(handler, FileHandler):
                        if handler._file_handle and not handler._file_handle.closed:
                            handler._file_handle.write(formatted_msg)
                            handler._file_handle.flush()
                except Exception:
                    pass

        finally:
            self.shutdown()
            sys.exit(1)

    def _global_exception_handler(self, exc_type, exc_value, exc_traceback):
        """Глобальный обработчик необработанных исключений"""
        if issubclass(exc_type, KeyboardInterrupt):
            sys.__excepthook__(exc_type, exc_value, exc_traceback)
            return

        self.e("Unhandled exception", exc_info=(exc_type, exc_value, exc_traceback))

    # Методы для разных уровней логирования (сохранена обратная совместимость)
    def i(self, msg: str) -> None:
        """Информационное сообщение"""
        self._log('info', msg)

    def i_h(self, msg: str) -> None:
        """Выделенное информационное сообщение"""
        self._log('info_hi', msg)

    def w(self, msg: str) -> None:
        """Предупреждение"""
        self._log('warn', msg)

    def e(self, msg: str, exc_info: Optional[tuple] = None) -> None:
        """Ошибка"""
        if exc_info:
            msg = f"{msg}\n{''.join(traceback.format_exception(*exc_info))}"
        self._log('error', msg)

    def f(self, msg: str) -> None:
        """Фатальная ошибка"""
        self._log('fatal', msg, is_fatal=True)

    def d(self, msg: str) -> None:
        """Отладочная информация"""
        self._log('debug', msg)

    def dd(self, msg: str) -> None:
        """Подробная отладочная информация"""
        self._log('deep-debug', msg)

    def shutdown(self) -> None:
        """Корректное завершение работы логгера"""
        with self._lock:
            if self._closed:
                return

            self._closed = True
            for handler in self._handlers:
                try:
                    handler.close()
                except Exception as e:
                    print(f"Handler {handler.name} close error: {e}", flush=True)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.shutdown()

    @classmethod
    def get_instance(cls, config: Optional[dict] = None) -> 'SpiderLog':
        """Получение singleton-экземпляра логгера"""
        with cls._lock:
            if cls._instance is None or cls._instance._closed:
                cls._instance = cls(config)
            return cls._instance


# Функции для быстрого доступа
def get_logger(config: Optional[dict] = None) -> SpiderLog:
    return SpiderLog.get_instance(config)


def setup_logging(config: Optional[dict] = None) -> SpiderLog:
    return SpiderLog.get_instance(config)


# ПРОСТОЙ ТЕСТ ДЛЯ ПРОВЕРКИ
if __name__ == "__main__":
    print("=== ТЕСТ ЛОГГЕРА ===")

    # Создаем логгер с конфигом по умолчанию
    logger = SpiderLog()

    # Тестируем все уровни логирования
    logger.i("Обычное информационное сообщение")
    logger.i_h("Важное информационное сообщение")
    logger.w("Предупреждение о чем-то")
    logger.d("Отладочная информация")
    logger.dd("Подробная отладочная информация")
    logger.e("Сообщение об ошибке")

    # Тест исключения
    try:
        result = 1 / 0
    except Exception as ex:
        logger.e("Ошибка деления", exc_info=sys.exc_info())

    # Тест производительности
    import time

    start = time.time()
    for i in range(1000000):  # Уменьшил количество для скорости теста
        logger.i(f"Тестовое сообщение {i}")

    logger.i(f"Время обработки 1000000 сообщений: {time.time() - start:.3f} сек")

    # Проверяем что файл создался и не пустой
    log_file_path = os.path.join('logs', 'main.log')
    if os.path.exists(log_file_path):
        with open(log_file_path, 'r', encoding='utf-8') as f:
            content = f.read()
            print(f"Размер лог-файла: {len(content)} байт")
            print("Первые 3 строки лог-файла:")
            for i, line in enumerate(content.splitlines()[:3]):
                print(f"{i + 1}: {line}")
    else:
        print(f"Лог-файл не создан: {log_file_path}")

    # Завершаем работу
    logger.shutdown()
    print("=== ТЕСТ ЗАВЕРШЕН ===")