#!/usr/bin/env python3
"""
Клиент для отправки логов на удаленный сервер LogBoard
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""

import asyncio
import json
import logging
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

import aiofiles
import aiohttp
import docker
from docker.errors import DockerException

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('/var/log/logboard-client.log')
    ]
)
logger = logging.getLogger(__name__)


class LogBoardClient:
    """Клиент для отправки логов в LogBoard сервер"""
    
    def __init__(self, server_url: str, api_key: str, hostname: str):
        """
        Инициализация клиента
        
        Args:
            server_url: URL сервера LogBoard
            api_key: API ключ для аутентификации
            hostname: Имя хоста для идентификации
        """
        self.server_url = server_url.rstrip('/')
        self.api_key = api_key
        self.hostname = hostname
        self.session: Optional[aiohttp.ClientSession] = None
        try:
            # Используем тот же способ, что и в основном сервисе
            self.docker_client = docker.from_env()
            # Проверяем подключение
            self.docker_client.ping()
            logger.info("Docker клиент успешно инициализирован")
        except Exception as e:
            logger.error(f"Критическая ошибка Docker клиента: {e}")
            raise
        
    async def __aenter__(self):
        """Асинхронный контекстный менеджер - вход"""
        self.session = aiohttp.ClientSession(
            headers={
                'Authorization': f'Bearer {self.api_key}',
                'Content-Type': 'application/json',
                'User-Agent': 'LogBoard-Client/1.0'
            }
        )
        return self
        
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Асинхронный контекстный менеджер - выход"""
        if self.session:
            await self.session.close()
            
    async def send_logs(self, container_name: str, logs: List[str]) -> bool:
        """
        Отправка логов на сервер
        
        Args:
            container_name: Имя контейнера
            logs: Список строк логов
            
        Returns:
            bool: True если отправка успешна, False в противном случае
        """
        if not self.session:
            logger.error("Сессия не инициализирована")
            return False
            
        payload = {
            "hostname": self.hostname,
            "container_name": container_name,
            "logs": logs,
            "timestamp": datetime.utcnow().isoformat()
        }
        
        try:
            async with self.session.post(
                f"{self.server_url}/api/logs/remote",
                json=payload,
                timeout=aiohttp.ClientTimeout(total=30)
            ) as response:
                if response.status == 200:
                    logger.info(f"Логи контейнера {container_name} успешно отправлены")
                    return True
                else:
                    logger.error(f"Ошибка отправки логов: {response.status} - {await response.text()}")
                    return False
        except Exception as e:
            logger.error(f"Ошибка при отправке логов: {e}")
            return False
            
    def get_containers(self) -> List[Dict]:
        """
        Получение списка контейнеров Docker
        
        Returns:
            List[Dict]: Список контейнеров с информацией
        """
        try:
            containers = []
            for container in self.docker_client.containers.list():
                containers.append({
                    "id": container.id,
                    "name": container.name,
                    "status": container.status,
                    "image": container.image.tags[0] if container.image.tags else container.image.id,
                    "created": container.attrs['Created']
                })
            return containers
        except DockerException as e:
            logger.error(f"Ошибка при получении списка контейнеров: {e}")
            return []
            
    async def collect_container_logs(self, container_name: str, lines: int = 100) -> List[str]:
        """
        Сбор логов контейнера
        
        Args:
            container_name: Имя контейнера
            lines: Количество строк логов для сбора
            
        Returns:
            List[str]: Список строк логов
        """
        try:
            container = self.docker_client.containers.get(container_name)
            logs = container.logs(
                stdout=True,
                stderr=True,
                tail=lines,
                timestamps=True
            ).decode('utf-8')
            
            return logs.splitlines() if logs else []
        except DockerException as e:
            logger.error(f"Ошибка при получении логов контейнера {container_name}: {e}")
            return []
            
    async def monitor_containers(self, interval: int = 60):
        """
        Мониторинг контейнеров и отправка логов
        
        Args:
            interval: Интервал мониторинга в секундах
        """
        logger.info(f"Запуск мониторинга контейнеров с интервалом {interval} секунд")
        
        while True:
            try:
                containers = self.get_containers()
                logger.info(f"Найдено {len(containers)} контейнеров")
                
                for container in containers:
                    container_name = container['name']
                    if container['status'] == 'running':
                        logs = await self.collect_container_logs(container_name)
                        if logs:
                            await self.send_logs(container_name, logs)
                            
            except Exception as e:
                logger.error(f"Ошибка в мониторинге: {e}")
                
            await asyncio.sleep(interval)


async def main():
    """Основная функция"""
    # Получение переменных окружения
    server_url = os.getenv('LOGBOARD_SERVER_URL', 'http://localhost:8000')
    api_key = os.getenv('LOGBOARD_API_KEY')
    hostname = os.getenv('HOSTNAME', os.uname().nodename)
    interval = int(os.getenv('LOGBOARD_INTERVAL', '60'))
    
    if not api_key:
        logger.error("LOGBOARD_API_KEY не установлен")
        sys.exit(1)
        
    logger.info(f"Запуск LogBoard клиента для хоста: {hostname}")
    logger.info(f"Подключение к серверу: {server_url}")
    
    async with LogBoardClient(server_url, api_key, hostname) as client:
        await client.monitor_containers(interval)


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("Получен сигнал прерывания, завершение работы")
    except Exception as e:
        logger.error(f"Критическая ошибка: {e}")
        sys.exit(1)
