"""ServiceManager DAO - Docker SDK objektum szintű service kezelés.

High-level Docker service operations using Docker SDK objects directly,
following the BlackAnt "Docker as Object" design pattern.
"""

import logging
import time
import random
from typing import Optional, List, Dict, Any, Union
from dataclasses import dataclass, field

import docker
from docker.models.services import Service as DockerServiceObject
from docker.models.containers import Container as DockerContainer
from docker.errors import DockerException, NotFound, APIError

from ..utils.logging import get_logger
from ..exceptions import BlackAntDockerError


@dataclass
class ServiceConfig:
    """Service configuration data class for ServiceManagerDAO."""

    name: str = field(default="")
    image: str = field(default="")
    command: Optional[List[str]] = field(default=None)
    environments: Dict[str, str] = field(default_factory=dict)
    networks: List[str] = field(default_factory=list)
    labels: Dict[str, str] = field(default_factory=dict)
    constraints: List[str] = field(default_factory=list)
    replicas: int = field(default=1)
    cpu_limit: Optional[int] = field(default=None)  # nanocpus
    memory_limit: Optional[int] = field(default=None)  # bytes
    ports: Dict[str, int] = field(default_factory=dict)
    mounts: List[Dict[str, str]] = field(default_factory=list)


class ServiceManagerDAO:
    """Docker Services objektum szintű kezelése.

    Provides high-level service management operations using Docker SDK
    objects directly instead of HTTP requests. This follows the BlackAnt
    "Docker as Object" pattern for internal server-side operations.

    Examples:
        >>> dao = ServiceManagerDAO()
        >>> config = ServiceConfig(name="my-service", image="nginx:latest")
        >>> service = dao.create_service(config)
        >>> services = dao.list_services()
        >>> dao.remove_service(service.id)
    """

    def __init__(self, docker_host: Optional[str] = None):
        """ServiceManager DAO inicializálása Docker client-tel.

        Args:
            docker_host: Optional Docker daemon host URL

        Raises:
            BlackAntDockerError: Ha a Docker client inicializálás sikertelen
        """
        self.logger = get_logger("service-manager-dao")

        try:
            if docker_host:
                self.__docker_client = docker.DockerClient(base_url=docker_host)
            else:
                self.__docker_client = docker.from_env()

            # Test connection
            self.__docker_client.ping()
            self.logger.info("ServiceManagerDAO initialized successfully")

        except DockerException as docker_error:
            error_msg = f"Docker client initialization failed: {docker_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from docker_error

    def create_service(self, service_config: Union[ServiceConfig, Dict[str, Any]]) -> DockerServiceObject:
        """Szolgáltatás létrehozása Docker SDK-val.

        Args:
            service_config: ServiceConfig object or configuration dictionary

        Returns:
            DockerServiceObject: Létrehozott Docker service objektum

        Raises:
            BlackAntDockerError: Docker API hiba esetén
        """
        # Convert dict to ServiceConfig if needed
        if isinstance(service_config, dict):
            config = ServiceConfig(**service_config)
        else:
            config = service_config

        try:
            # Prepare service creation parameters
            create_params = {
                'name': config.name,
                'image': config.image,
                'env': [f"{k}={v}" for k, v in config.environments.items()],
                'networks': config.networks,
                'labels': config.labels,
                'mode': docker.types.ServiceMode('replicated', config.replicas)
            }

            # Add command if specified
            if config.command:
                create_params['command'] = config.command

            # Add resource constraints if specified
            if config.cpu_limit or config.memory_limit:
                resources = docker.types.Resources(
                    cpu_limit=config.cpu_limit,
                    mem_limit=config.memory_limit
                )
                create_params['resources'] = resources

            # Add placement constraints
            if config.constraints:
                create_params['constraints'] = config.constraints

            # Add port mappings if specified
            if config.ports:
                endpoint_spec = docker.types.EndpointSpec(
                    ports={port: target for port, target in config.ports.items()}
                )
                create_params['endpoint_spec'] = endpoint_spec

            # Add mounts if specified
            if config.mounts:
                mounts = []
                for mount_config in config.mounts:
                    mount = docker.types.Mount(
                        target=mount_config['target'],
                        source=mount_config['source'],
                        type=mount_config.get('type', 'bind'),
                        read_only=mount_config.get('read_only', False)
                    )
                    mounts.append(mount)
                create_params['mounts'] = mounts

            # Create service using Docker SDK
            service = self.__docker_client.services.create(**create_params)

            self.logger.info(f"Service created successfully: {service.name} (ID: {service.id})")
            return service

        except APIError as api_error:
            error_msg = f"Service creation failed for '{config.name}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error
        except Exception as error:
            error_msg = f"Unexpected error creating service '{config.name}': {error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from error

    def get_service(self, service_id: str) -> Optional[DockerServiceObject]:
        """Szolgáltatás lekérése ID vagy név alapján.

        Args:
            service_id: Service ID vagy név

        Returns:
            DockerServiceObject vagy None ha nem található

        Raises:
            BlackAntDockerError: Docker API hiba esetén (kivéve NotFound)
        """
        try:
            service = self.__docker_client.services.get(service_id)
            self.logger.debug(f"Service found: {service_id}")
            return service

        except NotFound:
            self.logger.warning(f"Service not found: {service_id}")
            return None

        except APIError as api_error:
            error_msg = f"Error getting service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def list_services(self, filters: Optional[Dict[str, Any]] = None) -> List[DockerServiceObject]:
        """Szolgáltatások listázása.

        Args:
            filters: Docker API szűrők (pl. {'label': 'env=prod'})

        Returns:
            List[DockerServiceObject]: Service objektumok listája

        Raises:
            BlackAntDockerError: Docker API hiba esetén
        """
        try:
            services = self.__docker_client.services.list(filters=filters)
            self.logger.debug(f"Found {len(services)} services")
            return services

        except APIError as api_error:
            error_msg = f"Error listing services: {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def remove_service(self, service_id: str) -> bool:
        """Szolgáltatás eltávolítása.

        Args:
            service_id: Service ID vagy név

        Returns:
            bool: True ha sikeresen eltávolítva, False ha nem található

        Raises:
            BlackAntDockerError: Docker API hiba esetén (kivéve NotFound)
        """
        try:
            service = self.get_service(service_id)
            if service:
                service.remove()
                self.logger.info(f"Service removed successfully: {service_id}")
                return True
            else:
                self.logger.warning(f"Service not found for removal: {service_id}")
                return False

        except APIError as api_error:
            error_msg = f"Error removing service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def update_service(self, service_id: str, **kwargs) -> Optional[DockerServiceObject]:
        """Szolgáltatás frissítése.

        Args:
            service_id: Service ID vagy név
            **kwargs: Frissítendő paraméterek (image, env, labels, stb.)

        Returns:
            DockerServiceObject vagy None ha nem található

        Raises:
            BlackAntDockerError: Docker API hiba esetén
        """
        try:
            service = self.get_service(service_id)
            if service:
                # Refresh service object to get latest version
                service.reload()

                # Update service with provided parameters
                service.update(**kwargs)

                self.logger.info(f"Service updated successfully: {service_id}")
                return service
            else:
                self.logger.warning(f"Service not found for update: {service_id}")
                return None

        except APIError as api_error:
            error_msg = f"Error updating service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def scale_service(self, service_id: str, replicas: int) -> Optional[DockerServiceObject]:
        """Szolgáltatás méretezése (replicas számának változtatása).

        Args:
            service_id: Service ID vagy név
            replicas: Új replicas szám

        Returns:
            DockerServiceObject vagy None ha nem található

        Raises:
            BlackAntDockerError: Docker API hiba esetén
        """
        try:
            service = self.get_service(service_id)
            if service:
                # Scale service by updating replica count
                mode = docker.types.ServiceMode('replicated', replicas)
                service.update(mode=mode)

                self.logger.info(f"Service scaled successfully: {service_id} -> {replicas} replicas")
                return service
            else:
                self.logger.warning(f"Service not found for scaling: {service_id}")
                return None

        except APIError as api_error:
            error_msg = f"Error scaling service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def get_service_logs(self, service_id: str, **kwargs) -> str:
        """Szolgáltatás naplóinak lekérése.

        Args:
            service_id: Service ID vagy név
            **kwargs: Log opciók (tail, since, follow, stb.)

        Returns:
            str: Service logs

        Raises:
            BlackAntDockerError: Ha a service nem található vagy API hiba
        """
        try:
            service = self.get_service(service_id)
            if service:
                logs = service.logs(**kwargs)
                # Convert bytes to string if needed
                if isinstance(logs, bytes):
                    logs = logs.decode('utf-8')

                self.logger.debug(f"Retrieved logs for service: {service_id}")
                return logs
            else:
                raise BlackAntDockerError(f"Service not found: {service_id}")

        except APIError as api_error:
            error_msg = f"Error getting logs for service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def is_service_running(self, service_id: str) -> bool:
        """Ellenőrzi, hogy a szolgáltatásnak van-e futó task-ja.

        Args:
            service_id: Service ID vagy név

        Returns:
            bool: True ha van futó task, False egyébként
        """
        try:
            service = self.get_service(service_id)
            if service:
                # Check if service has any running tasks
                tasks = service.tasks()
                for task in tasks:
                    if task.get('Status', {}).get('State') == 'running':
                        return True
                return False
            else:
                return False

        except Exception as error:
            self.logger.error(f"Error checking service status '{service_id}': {error}")
            return False

    def wait_for_service_ready(self, service_id: str, timeout: int = 300) -> bool:
        """Várakozás amíg a szolgáltatás futó állapotba kerül.

        Args:
            service_id: Service ID vagy név
            timeout: Maximum várakozási idő másodpercekben

        Returns:
            bool: True ha a szolgáltatás futó állapotba került, False timeout esetén
        """
        self.logger.info(f"Waiting for service to become ready: {service_id}")

        start_time = time.time()
        while time.time() - start_time < timeout:
            if self.is_service_running(service_id):
                self.logger.info(f"Service is ready: {service_id}")
                return True

            # Random sleep to avoid overwhelming the API
            sleep_time = random.uniform(0.5, 2.0)
            time.sleep(sleep_time)

        self.logger.warning(f"Service did not become ready within {timeout}s: {service_id}")
        return False

    def get_service_tasks(self, service_id: str) -> List[Dict[str, Any]]:
        """Szolgáltatás task-jainak lekérése.

        Args:
            service_id: Service ID vagy név

        Returns:
            List[Dict]: Service task információk listája

        Raises:
            BlackAntDockerError: Ha a service nem található vagy API hiba
        """
        try:
            service = self.get_service(service_id)
            if service:
                tasks = service.tasks()
                self.logger.debug(f"Found {len(tasks)} tasks for service: {service_id}")
                return tasks
            else:
                raise BlackAntDockerError(f"Service not found: {service_id}")

        except APIError as api_error:
            error_msg = f"Error getting tasks for service '{service_id}': {api_error}"
            self.logger.error(error_msg)
            raise BlackAntDockerError(error_msg) from api_error

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - cleanup resources."""
        if hasattr(self.__docker_client, "close"):
            self.__docker_client.close()