"""
Django Revolution Dynamic Zone Management

Dynamic URL pattern generation for API zones without static files.
"""

from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
import importlib.util
import sys
from types import ModuleType

from django.urls import path, include
from django.apps import apps

from .config import DjangoRevolutionSettings, ZoneModel
from .utils import Logger, ErrorHandler


class DynamicZoneManager:
    """Manages API zones with dynamic URL pattern generation."""

    def __init__(self, config: DjangoRevolutionSettings):
        """Initialize zone manager with configuration."""
        self.config = config
        self.logger = Logger("dynamic_zone_manager")
        self.error_handler = ErrorHandler(self.logger)

        # Track found and missing apps during URL generation
        self.found_apps = []
        self.missing_apps = []

        # Cache for dynamically created modules
        self._zone_modules_cache = {}

    @property
    def zones(self) -> Dict[str, ZoneModel]:
        """Get validated zone models."""
        return self.config.get_zones()

    def get_app_urls(self, apps_list: List[str], prefix: str = None) -> List[Any]:
        """
        Generate URL patterns for apps that have urls.py.

        Args:
            apps_list: List of app names
            prefix: URL prefix (defaults to config.api_prefix)

        Returns:
            List of URL patterns
        """

        if prefix is None:
            prefix = self.config.api_prefix

        patterns = []
        found_apps = []
        missing_apps = []

        for app in apps_list:
            app_name = app.split(".")[-1]

            try:
                # Check if app is installed
                if not apps.is_installed(app):
                    missing_apps.append(app)
                    self.logger.warning(f"App not installed: {app}")
                    continue

                # Try to import URLs
                module_path = f"{app}.urls"
                patterns.append(path(f"{app_name}/", include(module_path)))
                found_apps.append(app)
                self.logger.debug(f"Added URL pattern for {app}")

            except ImportError as e:
                missing_apps.append(app)
                self.logger.warning(f"URLs not found for {app}: {e}")
            except Exception as e:
                missing_apps.append(app)
                self.logger.error(f"Error loading URLs for {app}: {e}")

        # Update tracking
        self.found_apps.extend(found_apps)
        self.missing_apps.extend(missing_apps)

        return patterns

    def create_dynamic_urlconf_module(
        self, zone_name: str, zone: ZoneModel
    ) -> ModuleType:
        """
        Create a dynamic URL configuration module in memory.

        Args:
            zone_name: Name of the zone
            zone: Zone model configuration

        Returns:
            ModuleType: Dynamically created module with urlpatterns
        """

        # Check cache first
        if zone_name in self._zone_modules_cache:
            return self._zone_modules_cache[zone_name]

        # Create module content
        module_content = f'''
"""
Dynamic URL configuration for zone: {zone_name}
Generated by Django Revolution
"""
from django.urls import path, include

# Zone-specific URL patterns for {zone_name}
urlpatterns = [
'''

        # Add URL patterns for each app in the zone
        for app in zone.apps:
            app_name = app.split(".")[-1]
            module_content += f'    path("{self.config.api_prefix}/{app_name}/", include("{app}.urls")),\n'

        module_content += "]\n"

        # Create module dynamically
        module_name = f"django_revolution_dynamic_{zone_name}_urls"
        spec = importlib.util.spec_from_loader(module_name, loader=None)
        module = importlib.util.module_from_spec(spec)

        # Execute module content
        exec(module_content, module.__dict__)

        # Add module to sys.modules for import resolution
        sys.modules[module_name] = module

        # Cache the module
        self._zone_modules_cache[zone_name] = module

        self.logger.debug(f"Created dynamic URL module for zone: {zone_name}")
        return module

    def create_zone_schema_patterns(self, zone_name: str, zone: ZoneModel) -> List[Any]:
        """
        Create schema URL patterns for a specific zone using dynamic modules.

        Args:
            zone_name: Name of the zone
            zone: Zone model

        Returns:
            List of URL patterns for schema endpoints
        """

        # Create the dynamic urlconf module for this zone
        urlconf_module = self.create_dynamic_urlconf_module(zone_name, zone)

        if not urlconf_module:
            return []

        schema_view_name = f"{zone_name}-schema"
        swagger_view_name = f"{zone_name}-swagger"

        try:
            from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

            return [
                # Schema endpoint - uses dynamic urlconf
                path(
                    "schema/",
                    SpectacularAPIView.as_view(urlconf=urlconf_module),
                    name=schema_view_name,
                ),
                # Swagger UI endpoint
                path(
                    "schema/swagger/",
                    SpectacularSwaggerView.as_view(url_name=schema_view_name),
                    name=swagger_view_name,
                ),
            ]
        except ImportError:
            self.logger.warning(
                "drf_spectacular not available, cannot create schema patterns"
            )
            return []
        except Exception as e:
            self.logger.error(f"Failed to create schema patterns for {zone_name}: {e}")
            return []

    def get_zone_urls(self) -> List[Any]:
        """
        Generate URL patterns for all zones using dynamic modules.

        Returns:
            List of zone URL patterns
        """

        zone_patterns = []

        for zone_name, zone in self.zones.items():
            try:
                zone_schema_patterns = self.create_zone_schema_patterns(zone_name, zone)
                if zone_schema_patterns:
                    zone_patterns.append(
                        path(f"schema/{zone_name}/", include(zone_schema_patterns))
                    )
                    self.logger.debug(f"Added dynamic zone patterns for: {zone_name}")
            except Exception as e:
                self.logger.error(
                    f"Failed to create patterns for zone {zone_name}: {e}"
                )

        return zone_patterns

    def get_zone_schema_urls(self) -> List[Any]:
        """
        Get schema URLs for all zones.

        Returns:
            List of schema URL patterns
        """
        return self.get_zone_urls()

    def get_zone_app_patterns(self, zone_name: str) -> List[Any]:
        """
        Get app URL patterns for a specific zone.

        Args:
            zone_name: Name of the zone

        Returns:
            List of app URL patterns
        """
        zone = self.zones.get(zone_name)
        if not zone:
            self.logger.warning(f"Zone not found: {zone_name}")
            return []

        return self.get_app_urls(zone.apps)

    def get_all_zone_patterns(self) -> Dict[str, List[Any]]:
        """
        Get URL patterns for all zones organized by zone name.

        Returns:
            Dictionary mapping zone names to their URL patterns
        """
        patterns = {}

        for zone_name, zone in self.zones.items():
            try:
                # Get app patterns
                app_patterns = self.get_app_urls(zone.apps)

                # Get schema patterns
                schema_patterns = self.create_zone_schema_patterns(zone_name, zone)

                patterns[zone_name] = {
                    "apps": app_patterns,
                    "schema": schema_patterns,
                    "total": len(app_patterns) + len(schema_patterns),
                }

            except Exception as e:
                self.logger.error(f"Failed to get patterns for zone {zone_name}: {e}")
                patterns[zone_name] = {"apps": [], "schema": [], "total": 0}

        return patterns

    def get_info(self) -> Dict[str, Any]:
        """
        Get information about zones and apps.

        Returns:
            Dictionary with zone and app information
        """
        return {
            "zones": {name: zone.model_dump() for name, zone in self.zones.items()},
            "found_apps": self.found_apps,
            "missing_apps": self.missing_apps,
            "total_zones": len(self.zones),
            "total_found_apps": len(self.found_apps),
            "total_missing_apps": len(self.missing_apps),
            "dynamic_modules_created": len(self._zone_modules_cache),
        }

    def clear_cache(self):
        """Clear the dynamic module cache."""
        self._zone_modules_cache.clear()
        self.logger.debug("Cleared dynamic module cache")


class DynamicZoneDetector:
    """Detects zones from various sources including Django configuration."""

    def __init__(
        self,
        config: Optional[DjangoRevolutionSettings] = None,
        logger: Optional[Logger] = None,
    ):
        """Initialize zone detector."""
        self.config = config
        self.logger = logger or Logger("dynamic_zone_detector")
        self.error_handler = ErrorHandler(self.logger)

    @property
    def zones(self) -> Dict[str, ZoneModel]:
        """Get zones from configuration."""
        if self.config:
            return self.config.get_zones()
        return {}

    def detect_zones(self) -> Dict[str, ZoneModel]:
        """
        Detect zones from all available sources.

        Returns:
            Dictionary of detected zones
        """
        zones = {}

        # Try to detect from Django configuration
        django_zones = self.detect_zones_from_django_config()
        zones.update(django_zones)

        # Use configuration zones
        if self.config:
            config_zones = self.config.get_zones()
            zones.update(config_zones)

        self.logger.info(f"Detected {len(zones)} zones")
        return zones

    def detect_zones_from_django_config(self) -> Dict[str, ZoneModel]:
        """
        Detect zones from Django settings or configuration.

        Returns:
            Dictionary of zones from Django config
        """
        zones = {}

        try:
            # Try to import Django settings
            from django.conf import settings

            # Look for API configuration
            if hasattr(settings, "API_ZONES"):
                for zone_name, zone_config in settings.API_ZONES.items():
                    zones[zone_name] = ZoneModel(name=zone_name, **zone_config)

            # Look for Django Revolution settings
            if hasattr(settings, "DJANGO_REVOLUTION"):
                revolution_config = settings.DJANGO_REVOLUTION
                if "zones" in revolution_config:
                    for zone_name, zone_config in revolution_config["zones"].items():
                        zones[zone_name] = ZoneModel(name=zone_name, **zone_config)

            # Try to import API config module
            try:
                from api.urls.config import APIConfig

                api_config = APIConfig()
                if hasattr(api_config, "zones"):
                    for zone_name, zone_config in api_config.zones.items():
                        zones[zone_name] = ZoneModel(name=zone_name, **zone_config)
            except ImportError:
                self.logger.debug("No api.urls.config.APIConfig found")

        except ImportError:
            self.logger.debug("Django not available for zone detection")
        except Exception as e:
            self.logger.warning(f"Error detecting zones from Django config: {e}")

        return zones

    def get_zone_names(self) -> List[str]:
        """Get list of zone names."""
        return list(self.zones.keys())

    def get_zone(self, name: str) -> Optional[ZoneModel]:
        """Get specific zone by name."""
        return self.zones.get(name)

    def validate_zone(self, zone_name: str) -> bool:
        """Validate if zone exists."""
        return zone_name in self.zones

    def validate_zone_apps(self, zone_name: str) -> bool:
        """
        Validate if all apps in a zone are available.

        Args:
            zone_name: Name of the zone to validate

        Returns:
            bool: True if all apps are valid
        """

        zone = self.get_zone(zone_name)
        if not zone:
            return False

        try:
            for app in zone.apps:
                if not apps.is_installed(app):
                    return False
            return True
        except Exception:
            return False

    def get_zone_apps(self, zone_name: str) -> List[str]:
        """
        Get apps for a specific zone.

        Args:
            zone_name: Name of the zone

        Returns:
            List of app names
        """
        zone = self.get_zone(zone_name)
        return zone.apps if zone else []

    def get_all_apps(self) -> List[str]:
        """
        Get all unique apps across all zones.

        Returns:
            List of all app names
        """
        all_apps = set()
        for zone in self.zones.values():
            all_apps.update(zone.apps)
        return sorted(list(all_apps))

    def get_zones_summary(self) -> Dict[str, Any]:
        """
        Get summary of detected zones.

        Returns:
            Dictionary with zone summary information
        """
        zones_info = {}
        for zone_name, zone in self.zones.items():
            zones_info[zone_name] = {
                "title": zone.title,
                "description": zone.description,
                "apps": zone.apps,
                "app_count": len(zone.apps),
                "public": zone.public,
                "auth_required": zone.auth_required,
                "version": zone.version,
                "path_prefix": zone.path_prefix,
            }

        return {
            "total_zones": len(self.zones),
            "zone_names": list(self.zones.keys()),
            "zones": zones_info,
        }


# Backward compatibility aliases
ZoneManager = DynamicZoneManager
ZoneDetector = DynamicZoneDetector
