"""
URL Configuration Discovery Module

Scans Django's URLconf to automatically discover all DRF APIView and ViewSet classes.
Implements efficient recursive traversal with startup caching.
"""

import logging
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Set, Type

from django.urls import URLPattern, URLResolver
from django.urls.exceptions import Resolver404
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ViewSet

logger = logging.getLogger(__name__)


@dataclass
class EndpointMetadata:
    """Metadata for a discovered DRF endpoint"""

    view_class: Type[Any]
    path: str
    http_methods: List[str]
    is_viewset: bool
    basename: Optional[str] = None
    actions: Optional[Dict[str, str]] = None  # {method: action_name}


class EndpointScanner:
    """
    Recursively scans Django URLconf to discover DRF APIView and ViewSet endpoints.
    Caches results at startup for efficient lookup.
    
    Usage:
        scanner = EndpointScanner()
        endpoints = scanner.scan()
    """

    def __init__(self, root_urlconf: Optional[str] = None):
        """
        Initialize scanner.
        
        Args:
            root_urlconf: Django ROOT_URLCONF setting. If None, uses Django settings.
        """
        self._root_urlconf = root_urlconf
        self._cache: Optional[List[EndpointMetadata]] = None
        self._visited_urls: Set[int] = set()

    def scan(self) -> List[EndpointMetadata]:
        """
        Scan URLconf and return all discovered DRF endpoints.
        Results are cached after first scan.
        
        Returns:
            List of EndpointMetadata objects for all discovered endpoints
        """
        if self._cache is not None:
            return self._cache

        self._cache = self._scan_urlconf()
        logger.info(
            f"Discovered {len(self._cache)} DRF endpoints from URLconf",
            extra={"endpoint_count": len(self._cache)},
        )
        return self._cache

    def _scan_urlconf(self) -> List[EndpointMetadata]:
        """
        Perform actual URLconf scanning.
        
        Returns:
            List of discovered endpoints
        """
        try:
            from django.conf import settings

            root_urlconf = self._root_urlconf or settings.ROOT_URLCONF
        except (ImportError, AttributeError) as e:
            logger.error(f"Failed to load ROOT_URLCONF: {e}")
            return []

        try:
            import importlib

            urlconf_module = importlib.import_module(root_urlconf)
        except (ImportError, AttributeError) as e:
            logger.error(f"Failed to import URLconf module {root_urlconf}: {e}")
            return []

        endpoints: List[EndpointMetadata] = []

        if hasattr(urlconf_module, "urlpatterns"):
            endpoints = self._traverse_patterns(
                urlconf_module.urlpatterns, prefix=""
            )

        return endpoints

    def _traverse_patterns(
        self, patterns: List[Any], prefix: str = ""
    ) -> List[EndpointMetadata]:
        """
        Recursively traverse URL patterns.
        
        Args:
            patterns: List of URLPattern or URLResolver objects
            prefix: Current URL path prefix
            
        Returns:
            List of discovered endpoints
        """
        endpoints: List[EndpointMetadata] = []

        for pattern in patterns:
            if isinstance(pattern, URLResolver):
                # Recursively traverse nested URLconf
                nested_prefix = prefix + str(pattern.pattern)
                nested_patterns = pattern.url_patterns
                endpoints.extend(
                    self._traverse_patterns(nested_patterns, nested_prefix)
                )

            elif isinstance(pattern, URLPattern):
                # Extract endpoint from URLPattern
                endpoint = self._extract_endpoint(pattern, prefix)
                if endpoint:
                    endpoints.append(endpoint)

        return endpoints

    def _extract_endpoint(
        self, pattern: URLPattern, prefix: str = ""
    ) -> Optional[EndpointMetadata]:
        """
        Extract endpoint metadata from a URLPattern.
        
        Args:
            pattern: URLPattern to extract from
            prefix: Current URL path prefix
            
        Returns:
            EndpointMetadata if this is a DRF endpoint, None otherwise
        """
        try:
            callback = pattern.callback
        except Resolver404:
            return None

        # Check if it's a view function (ignore function-based views for now)
        if not hasattr(callback, "cls"):
            return None

        view_class = callback.cls
        if not self._is_drf_view(view_class):
            return None

        # Avoid scanning the same view class multiple times
        view_id = id(view_class)
        if view_id in self._visited_urls:
            return None

        self._visited_urls.add(view_id)

        path = prefix + str(pattern.pattern)
        http_methods = self._get_http_methods(view_class)
        is_viewset = issubclass(view_class, ViewSet)
        basename = getattr(pattern, "name", None)
        actions = None

        if is_viewset:
            actions = self._extract_viewset_actions(view_class)

        return EndpointMetadata(
            view_class=view_class,
            path=path,
            http_methods=http_methods,
            is_viewset=is_viewset,
            basename=basename,
            actions=actions,
        )

    @staticmethod
    def _is_drf_view(view_class: Type[Any]) -> bool:
        """Check if a view class is a DRF APIView or ViewSet"""
        try:
            return issubclass(view_class, (GenericAPIView, ViewSet))
        except TypeError:
            return False

    @staticmethod
    def _get_http_methods(view_class: Type[Any]) -> List[str]:
        """Extract HTTP methods supported by a view class"""
        methods = []
        http_methods = ["get", "post", "put", "patch", "delete", "head", "options"]

        for method in http_methods:
            if hasattr(view_class, method):
                methods.append(method.upper())

        return methods or ["GET"]

    @staticmethod
    def _extract_viewset_actions(view_class: Type[Any]) -> Dict[str, str]:
        """
        Extract custom @action decorators from a ViewSet.
        
        Returns:
            Dict mapping HTTP method to action name
        """
        actions: Dict[str, str] = {}

        for attr_name in dir(view_class):
            attr = getattr(view_class, attr_name)
            if hasattr(attr, "mapping"):  # @action decorator sets 'mapping'
                for method, action_name in attr.mapping.items():
                    actions[method.upper()] = action_name

        return actions

    def clear_cache(self) -> None:
        """Clear cached endpoints to force re-scan on next access"""
        self._cache = None
        self._visited_urls.clear()
