# -*- coding: utf-8 -*-
"""Root Service Implementation.

Copyright 2025
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

This module implements root directory management according to the MCP specification.
It handles root registration, validation, and change notifications.
"""

# Standard
import asyncio
import logging
import os
from typing import AsyncGenerator, Dict, List, Optional
from urllib.parse import urlparse

# First-Party
from mcpgateway.config import settings
from mcpgateway.models import Root

logger = logging.getLogger(__name__)


class RootServiceError(Exception):
    """Base class for root service errors."""


class RootService:
    """MCP root service.

    Manages roots that can be exposed to MCP clients.
    Handles:
    - Root registration and validation
    - Change notifications
    - Root permissions and access control
    """

    def __init__(self):
        """Initialize root service."""
        self._roots: Dict[str, Root] = {}
        self._subscribers: List[asyncio.Queue] = []

    async def initialize(self) -> None:
        """Initialize root service."""
        logger.info("Initializing root service")
        # Add any configured default roots
        for root_uri in settings.default_roots:
            try:
                await self.add_root(root_uri)
            except RootServiceError as e:
                logger.error(f"Failed to add default root {root_uri}: {e}")

    async def shutdown(self) -> None:
        """Shutdown root service."""
        logger.info("Shutting down root service")
        # Clear all roots and subscribers
        self._roots.clear()
        self._subscribers.clear()

    async def list_roots(self) -> List[Root]:
        """List available roots.

        Returns:
            List of registered roots
        """
        return list(self._roots.values())

    async def add_root(self, uri: str, name: Optional[str] = None) -> Root:
        """Add a new root.

        Args:
            uri: Root URI
            name: Optional root name

        Returns:
            Created root object

        Raises:
            RootServiceError: If root is invalid or already exists
        """
        try:
            root_uri = self._make_root_uri(uri)
        except ValueError as e:
            raise RootServiceError(f"Invalid root URI: {e}")

        if root_uri in self._roots:
            raise RootServiceError(f"Root already exists: {root_uri}")

        # Skip any access check; just store the key/value.
        root_obj = Root(
            uri=root_uri,
            name=name or os.path.basename(urlparse(root_uri).path) or root_uri,
        )
        self._roots[root_uri] = root_obj

        await self._notify_root_added(root_obj)
        logger.info(f"Added root: {root_uri}")
        return root_obj

    async def remove_root(self, root_uri: str) -> None:
        """Remove a registered root.

        Args:
            root_uri: Root URI to remove

        Raises:
            RootServiceError: If root not found
        """
        if root_uri not in self._roots:
            raise RootServiceError(f"Root not found: {root_uri}")
        root_obj = self._roots.pop(root_uri)
        await self._notify_root_removed(root_obj)
        logger.info(f"Removed root: {root_uri}")

    async def subscribe_changes(self) -> AsyncGenerator[Dict, None]:
        """Subscribe to root changes.

        Yields:
            Root change events
        """
        queue: asyncio.Queue = asyncio.Queue()
        self._subscribers.append(queue)
        try:
            while True:
                event = await queue.get()
                yield event
        finally:
            self._subscribers.remove(queue)

    def _make_root_uri(self, uri: str) -> str:
        """Convert input to a valid URI.

        If no scheme is provided, assume a file URI and convert the path to an absolute path.

        Args:
            uri: Input URI or filesystem path

        Returns:
            A valid URI string
        """
        parsed = urlparse(uri)
        if not parsed.scheme:
            # No scheme provided; assume a file URI.
            return f"file://{uri}"
        # If a scheme is present (e.g., http, https, ftp, etc.), return the URI as-is.
        return uri

    async def _notify_root_added(self, root: Root) -> None:
        """Notify subscribers of root addition.

        Args:
            root: Added root
        """
        event = {"type": "root_added", "data": {"uri": root.uri, "name": root.name}}
        await self._notify_subscribers(event)

    async def _notify_root_removed(self, root: Root) -> None:
        """Notify subscribers of root removal.

        Args:
            root: Removed root
        """
        event = {"type": "root_removed", "data": {"uri": root.uri}}
        await self._notify_subscribers(event)

    async def _notify_subscribers(self, event: Dict) -> None:
        """Send event to all subscribers.

        Args:
            event: Event to send
        """
        for queue in self._subscribers:
            try:
                await queue.put(event)
            except Exception as e:
                logger.error(f"Failed to notify subscriber: {e}")
