"""BlackAnt Service Registry module.

Provides service registration, discovery, and lifecycle management
through BlackAnt's service registry API.
"""

import os
from typing import Dict, Any, Optional, List

from ..auth.blackant_auth import BlackAntAuth
from ..http.client import HTTPClient, HTTPConnectionError
from ..exceptions import BlackAntDockerError
from ..utils.logging import get_logger
from ..auth.role_assignment import get_role_assignment_manager

# Import ServiceManagerDAO for object-level Docker operations
try:
    from .dao import ServiceManagerDAO
except ImportError:
    ServiceManagerDAO = None


class ServiceRegistry:
    """Service registry for managing BlackAnt services.

    Handles service registration, discovery, metadata management,
    and lifecycle operations through BlackAnt's API.

    Args:
        auth (BlackAntAuth): Authentication object with credentials.
        base_url (str, optional): Base URL for BlackAnt API.

    Examples:
        >>> auth = BlackAntAuth(user="my_name", password="xxx")
        >>> registry = ServiceRegistry(auth=auth)
        >>> service_id = registry.register({
        ...     "name": "my-service",
        ...     "namespace": "default",
        ...     "metadata": {"version": "1.0.0"}
        ... })
        >>> services = registry.list(namespace="default")
    """

    def __init__(self, auth: BlackAntAuth, base_url: Optional[str] = None):
        """Initialize Service registry with authentication.

        Args:
            auth: BlackAntAuth object with user credentials.
            base_url: Optional base URL, defaults to environment variable.
        """
        self.auth = auth
        self.base_url = base_url or os.getenv(
            "BLACKANT_BASE_URL",
            "https://dev.blackant.app"
        )

        # Initialize HTTPClient with persistent session and connection pooling
        self.http_client = HTTPClient(
            base_url=self.base_url,
            auth_token_store=auth.token_store
        )

        self.logger = get_logger("services.registry")

        # Initialize ServiceManagerDAO for object-level Docker operations
        # This provides hybrid architecture: HTTP API (external) + Docker SDK (internal)
        try:
            self.service_dao = ServiceManagerDAO() if ServiceManagerDAO else None
            if self.service_dao:
                self.logger.info("ServiceManagerDAO initialized for object-level operations")
        except Exception as dao_error:
            self.logger.warning(f"ServiceManagerDAO initialization failed: {dao_error}")
            self.service_dao = None

        self.logger.info("ServiceRegistry initialized")

    def register(self, service_data: Dict[str, Any]) -> str:
        """Register a new service.

        MODIFIED: Added automatic role assignment after successful registration.

        Args:
            service_data: Service registration data containing:
                - name: Service name
                - namespace: Service namespace
                - metadata: Additional service metadata

        Returns:
            Service ID of the registered service.

        Raises:
            BlackAntDockerError: If service registration fails.
        """
        try:
            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint="/api/services/services",
                method="POST",
                json=service_data,
                token=token
            )
            response.raise_for_status()

            result = response.json()
            service_id = result.get("service_id", result.get("id"))
            self.logger.info(f"Service registered: {service_id}")

            # Post-publish hook - Automatic role assignment
            self._assign_role_after_publish(service_data, service_id)

            return service_id

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to register service: {error}")
            raise BlackAntDockerError(f"Could not register service: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error registering service: {error}")
            raise BlackAntDockerError(f"Service registration failed: {error}") from error

    def list(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]:
        """List all services, optionally filtered by namespace.

        Args:
            namespace: Optional namespace filter.

        Returns:
            List of service dictionaries.

        Raises:
            BlackAntDockerError: If listing services fails.
        """
        try:
            endpoint = "/api/services/services"
            if namespace:
                endpoint += f"?namespace={namespace}"

            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint=endpoint,
                method="GET",
                token=token
            )
            response.raise_for_status()

            services = response.json()
            self.logger.debug(f"Found {len(services)} services")
            return services

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to list services: {error}")
            raise BlackAntDockerError(f"Could not list services: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error listing services: {error}")
            raise BlackAntDockerError(f"Service list failed: {error}") from error

    def get(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> Dict[str, Any]:
        """Get metadata for a specific service.

        Args:
            service_id: Service ID to query.
            service_uuid: Service UUID to query.
            service_name: Service name to query.

        Returns:
            Service metadata dictionary.

        Raises:
            BlackAntDockerError: If getting service fails.
        """
        try:
            endpoint = "/api/services/services"
            params = []
            if service_id:
                params.append(f"id={service_id}")
            if service_uuid:
                params.append(f"uuid={service_uuid}")
            if service_name:
                params.append(f"name={service_name}")

            if params:
                endpoint += "?" + "&".join(params)

            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint=endpoint,
                method="GET",
                token=token
            )
            response.raise_for_status()

            service = response.json()
            self.logger.debug(f"Retrieved service: {service_id}")
            return service

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to get service {service_id}: {error}")
            raise BlackAntDockerError(f"Could not get service: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error getting service: {error}")
            raise BlackAntDockerError(f"Get service failed: {error}") from error

    def update(self, service_id: str = None, service_uuid: str = None, service_name: str = None, update_data: Dict[str, Any] = None) -> bool:
        """Update service metadata.

        Args:
            service_id: Service ID to update.
            service_uuid: Service UUID to update.
            service_name: Service name to update.
            update_data: Updated service data.

        Returns:
            True if update successful.

        Raises:
            BlackAntDockerError: If updating service fails.
        """
        try:
            # Note: Backend doesn't have PUT endpoint currently
            # This would need to be implemented in backend
            endpoint = "/api/services/services"
            params = []
            if service_id:
                params.append(f"id={service_id}")
            if service_uuid:
                params.append(f"uuid={service_uuid}")
            if service_name:
                params.append(f"name={service_name}")

            if params:
                endpoint += "?" + "&".join(params)
            else:
                raise ValueError("One of service_id, service_uuid, or service_name must be provided")

            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint=endpoint,
                method="PUT",
                json=update_data,
                token=token
            )
            response.raise_for_status()

            self.logger.info(f"Service updated: {service_id}")
            return True

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to update service {service_id}: {error}")
            raise BlackAntDockerError(f"Could not update service: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error updating service: {error}")
            raise BlackAntDockerError(f"Update service failed: {error}") from error

    def delete(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> bool:
        """Delete a service.

        Args:
            service_id: Service ID to delete.
            service_uuid: Service UUID to delete.
            service_name: Service name to delete.

        Returns:
            True if deletion successful.

        Raises:
            BlackAntDockerError: If deleting service fails.
        """
        try:
            endpoint = "/api/services/services"
            params = []
            if service_id:
                params.append(f"id={service_id}")
            if service_uuid:
                params.append(f"uuid={service_uuid}")
            if service_name:
                params.append(f"name={service_name}")

            if params:
                endpoint += "?" + "&".join(params)
            else:
                raise ValueError("One of service_id, service_uuid, or service_name must be provided")

            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint=endpoint,
                method="DELETE",
                token=token
            )
            response.raise_for_status()

            self.logger.info(f"Service deleted: {service_id}")
            return True

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to delete service {service_id}: {error}")
            raise BlackAntDockerError(f"Could not delete service: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error deleting service: {error}")
            raise BlackAntDockerError(f"Delete service failed: {error}") from error

    def get_status(self, service_id: str = None, service_uuid: str = None, service_name: str = None) -> Dict[str, Any]:
        """Get status of a specific service.

        Args:
            service_id: Service ID to check status.
            service_uuid: Service UUID to check status.
            service_name: Service name to check status.

        Returns:
            Service status dictionary containing:
                - status: Service status (running, stopped, error, etc.)
                - health: Health check results if available
                - metrics: Performance metrics if available

        Raises:
            BlackAntDockerError: If getting status fails.
        """
        try:
            # Note: Backend doesn't have status endpoint currently
            # Using GET /api/services/services with parameters (same as get())
            endpoint = "/api/services/services"
            params = []
            if service_id:
                params.append(f"id={service_id}")
            if service_uuid:
                params.append(f"uuid={service_uuid}")
            if service_name:
                params.append(f"name={service_name}")

            if params:
                endpoint += "?" + "&".join(params)
            else:
                raise ValueError("One of service_id, service_uuid, or service_name must be provided")

            # Get token from auth for authenticated request
            token = self.auth.get_token() if self.auth else None

            response = self.http_client.send_request(
                endpoint=endpoint,
                method="GET",
                token=token
            )
            response.raise_for_status()

            status = response.json()
            self.logger.debug(f"Retrieved status for service: {service_id}")
            return status

        except HTTPConnectionError as error:
            self.logger.error(f"Failed to get service status {service_id}: {error}")
            raise BlackAntDockerError(f"Could not get service status: {error}") from error
        except Exception as error:
            self.logger.error(f"Unexpected error getting service status: {error}")
            raise BlackAntDockerError(f"Get service status failed: {error}") from error

    # =============================================================================
    # OBJECT-LEVEL DOCKER OPERATIONS (Using ServiceManagerDAO)
    # =============================================================================

    def create_service_with_docker(self, service_config: Dict[str, Any]) -> Optional[str]:
        """Create Docker service using object-level operations.

        This method uses the ServiceManagerDAO for direct Docker SDK operations
        instead of HTTP API calls. Provides better performance and object-oriented
        interface for internal BlackAnt server operations.

        Args:
            service_config: Service configuration dictionary containing:
                - name: Service name
                - image: Docker image
                - environments: Environment variables dict
                - networks: Network list
                - labels: Labels dict
                - constraints: Placement constraints list
                - replicas: Replica count (default: 1)
                - cpu_limit: CPU limit in nanocpus
                - memory_limit: Memory limit in bytes

        Returns:
            Service ID if successful, None if ServiceManagerDAO not available

        Raises:
            BlackAntDockerError: If service creation fails

        Examples:
            >>> registry = ServiceRegistry(auth)
            >>> service_id = registry.create_service_with_docker({
            ...     "name": "my-app",
            ...     "image": "nginx:latest",
            ...     "environments": {"ENV": "prod"},
            ...     "replicas": 2
            ... })
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available, cannot create service with Docker")
            return None

        try:
            # Use ServiceManagerDAO for object-level Docker operations
            docker_service = self.service_dao.create_service(service_config)

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

        except Exception as error:
            self.logger.error(f"Failed to create Docker service: {error}")
            raise BlackAntDockerError(f"Docker service creation failed: {error}") from error

    def get_docker_service(self, service_id: str):
        """Get Docker service object using ServiceManagerDAO.

        Args:
            service_id: Service ID or name

        Returns:
            Docker service object or None if not found/DAO not available

        Examples:
            >>> service = registry.get_docker_service("my-service")
            >>> if service:
            ...     print(f"Service: {service.name}, Status: {service.attrs['Spec']}")
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available")
            return None

        try:
            return self.service_dao.get_service(service_id)
        except Exception as error:
            self.logger.error(f"Failed to get Docker service: {error}")
            return None

    def list_docker_services(self, filters: Optional[Dict[str, Any]] = None) -> List:
        """List Docker services using object-level operations.

        Args:
            filters: Docker API filters (e.g., {'label': 'env=prod'})

        Returns:
            List of Docker service objects, empty list if DAO not available

        Examples:
            >>> services = registry.list_docker_services({'label': 'env=prod'})
            >>> for service in services:
            ...     print(f"Service: {service.name}")
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available")
            return []

        try:
            return self.service_dao.list_services(filters=filters)
        except Exception as error:
            self.logger.error(f"Failed to list Docker services: {error}")
            return []

    def remove_docker_service(self, service_id: str) -> bool:
        """Remove Docker service using ServiceManagerDAO.

        Args:
            service_id: Service ID or name

        Returns:
            True if removed successfully, False if not found/DAO not available

        Examples:
            >>> success = registry.remove_docker_service("my-service")
            >>> if success:
            ...     print("Service removed successfully")
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available")
            return False

        try:
            return self.service_dao.remove_service(service_id)
        except Exception as error:
            self.logger.error(f"Failed to remove Docker service: {error}")
            return False

    def scale_docker_service(self, service_id: str, replicas: int) -> bool:
        """Scale Docker service using object-level operations.

        Args:
            service_id: Service ID or name
            replicas: Target replica count

        Returns:
            True if scaled successfully, False if failed/DAO not available

        Examples:
            >>> success = registry.scale_docker_service("my-service", 3)
            >>> if success:
            ...     print("Service scaled to 3 replicas")
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available")
            return False

        try:
            service = self.service_dao.scale_service(service_id, replicas)
            return service is not None
        except Exception as error:
            self.logger.error(f"Failed to scale Docker service: {error}")
            return False

    def get_service_logs_docker(self, service_id: str, **kwargs) -> Optional[str]:
        """Get service logs using ServiceManagerDAO.

        Args:
            service_id: Service ID or name
            **kwargs: Log options (tail, since, follow, etc.)

        Returns:
            Service logs as string, None if failed/DAO not available

        Examples:
            >>> logs = registry.get_service_logs_docker("my-service", tail=100)
            >>> if logs:
            ...     print(logs)
        """
        if not self.service_dao:
            self.logger.warning("ServiceManagerDAO not available")
            return None

        try:
            return self.service_dao.get_service_logs(service_id, **kwargs)
        except Exception as error:
            self.logger.error(f"Failed to get service logs: {error}")
            return None

    def is_docker_service_running(self, service_id: str) -> bool:
        """Check if Docker service is running using ServiceManagerDAO.

        Args:
            service_id: Service ID or name

        Returns:
            True if service has running tasks, False otherwise

        Examples:
            >>> if registry.is_docker_service_running("my-service"):
            ...     print("Service is running")
        """
        if not self.service_dao:
            return False

        try:
            return self.service_dao.is_service_running(service_id)
        except Exception as error:
            self.logger.error(f"Failed to check service status: {error}")
            return False

    @property
    def has_docker_support(self) -> bool:
        """Check if object-level Docker operations are available.

        Returns:
            True if ServiceManagerDAO is available and operational
        """
        return self.service_dao is not None

    def _assign_role_after_publish(
        self,
        service_data: Dict[str, Any],
        service_id: str
    ) -> None:
        """Post-publish hook: Assign science_module role to user.

        This method is called after successful service registration.
        Role assignment errors are logged but do not prevent service publication.

        Args:
            service_data: Service registration data (contains user token)
            service_id: ID of the registered service
        """
        try:
            # Check if automatic role assignment is enabled
            auto_assign_enabled = os.getenv('BLACKANT_AUTO_ASSIGN_ENABLED', 'true').lower() == 'true'
            if not auto_assign_enabled:
                self.logger.debug("Automatic role assignment is disabled")
                return

            # Get user token from auth token store
            user_token = self.auth.token_store.user_token if self.auth else None

            if not user_token:
                self.logger.warning(
                    "No user token available for role assignment, skipping"
                )
                return

            # Get role assignment manager
            role_manager = get_role_assignment_manager()

            # Attempt role assignment (non-blocking)
            service_name = service_data.get('name', 'unknown')
            metadata = {
                'service_id': service_id,
                'image': service_data.get('image'),
                'type': service_data.get('type'),
            }

            success = role_manager.assign_role_after_publish(
                user_token=user_token,
                service_name=service_name,
                metadata=metadata
            )

            if success:
                self.logger.info(
                    f"Role assignment successful for service '{service_name}' "
                    f"(service_id: {service_id})"
                )
            else:
                self.logger.warning(
                    f"Role assignment failed for service '{service_name}', "
                    f"but service was published successfully"
                )

        except Exception as e:
            # Catch all exceptions - role assignment must not prevent publication
            self.logger.warning(
                f"Role assignment error (service published successfully): {e}",
                exc_info=True
            )
