"""Service factory for creating provider instances based on configuration."""

import logging

from src.config import Config
from src.core.entities import MaliciousPackage
from src.core.interfaces import (
    NotificationService,
    PackagesFeed,
    PackagesRegistryService,
    StorageService,
)
from src.providers.exceptions import NotificationError
from src.providers.feeds.memory_feed import MemoryFeed
from src.providers.feeds.osv_feed import OSVFeed
from src.providers.notifications.msteams_notifier import MSTeamsNotifier
from src.providers.notifications.null_notifier import NullNotifier
from src.providers.notifications.webhook_notifier import WebhookNotifier
from src.providers.registries.jfrog_registry import JFrogRegistry
from src.providers.registries.null_registry import NullRegistry
from src.providers.storage.database_storage import DatabaseStorage
from src.providers.storage.file_storage import FileStorage
from src.providers.storage.memory_storage import MemoryStorage

logger = logging.getLogger(__name__)


class ServiceFactoryError(Exception):
    """Exception raised by ServiceFactory."""

    pass


class ServiceFactory:
    """Factory for creating service instances based on configuration."""

    def __init__(self, config: Config):
        """
        Initialize service factory with configuration.

        Args:
            config: Application configuration
        """
        self.config = config

    def create_packages_feed(self) -> PackagesFeed:
        """
        Create packages feed provider based on configuration.

        Returns:
            PackagesFeed instance

        Raises:
            ServiceFactoryError: If provider creation fails
        """
        logger.debug(
            f"Creating packages feed provider: {self.config.packages_feed.type}"
        )

        try:
            if self.config.packages_feed.type.lower() == "osv":
                return OSVFeed(
                    bucket_name=self.config.packages_feed.config.get(
                        "bucket_name", "osv-vulnerabilities"
                    ),
                    timeout_seconds=self.config.packages_feed.config.get(
                        "timeout_seconds", 30
                    ),
                    max_retries=self.config.packages_feed.config.get("max_retries", 3),
                    retry_delay=self.config.packages_feed.config.get(
                        "retry_delay", 1.0
                    ),
                )
            elif self.config.packages_feed.type.lower() == "memory":
                # Memory feed for testing - convert YAML packages to MaliciousPackage objects
                packages_data = self.config.packages_feed.config.get("packages", [])
                packages = self._convert_packages_data_to_entities(packages_data)
                return MemoryFeed(packages=packages)
            else:
                raise ServiceFactoryError(
                    f"Unknown packages feed type: {self.config.packages_feed.type}"
                )

        except Exception as e:
            raise ServiceFactoryError(f"Failed to create packages feed: {e}") from e

    def create_packages_registry(self) -> PackagesRegistryService:
        """
        Create packages registry service based on configuration.

        Returns:
            PackagesRegistryService instance

        Raises:
            ServiceFactoryError: If provider creation fails
        """
        logger.debug(
            f"Creating packages registry service: {self.config.packages_registry.type}"
        )

        # Check if packages registry is disabled
        if not self.config.packages_registry.enabled:
            logger.info("Packages registry is disabled, creating null registry")
            return self._create_null_registry()

        try:
            if self.config.packages_registry.type.lower() == "null":
                logger.info("Creating null packages registry for testing")
                return self._create_null_registry()
            elif self.config.packages_registry.type.lower() == "jfrog":
                if not self.config.jfrog_base_url:
                    raise ServiceFactoryError("JFrog base URL not configured")

                # Extract repository discovery configuration
                repository_config = self.config.packages_registry.config.get(
                    "repository_discovery", {}
                )
                ecosystem_overrides = repository_config.get("ecosystem_overrides", {})
                cache_ttl_seconds = repository_config.get("cache_ttl_seconds", 3600)

                return JFrogRegistry(
                    base_url=self.config.jfrog_base_url,
                    username=self.config.jfrog_username,
                    password=self.config.jfrog_password,
                    api_key=self.config.jfrog_api_key,
                    timeout_seconds=self.config.packages_registry.config.get(
                        "timeout_seconds", 30
                    ),
                    max_retries=self.config.packages_registry.config.get(
                        "max_retries", 3
                    ),
                    retry_delay=self.config.packages_registry.config.get(
                        "retry_delay", 1.0
                    ),
                    repository_overrides=ecosystem_overrides,
                    cache_ttl_seconds=cache_ttl_seconds,
                )
            else:
                raise ServiceFactoryError(
                    f"Unknown packages registry type: {self.config.packages_registry.type}"
                )

        except Exception as e:
            raise ServiceFactoryError(f"Failed to create packages registry: {e}") from e

    def create_notification_service(self) -> NotificationService:
        """
        Create notification service based on configuration.

        Returns:
            NotificationService instance

        Raises:
            ServiceFactoryError: If provider creation fails
        """
        logger.debug(
            f"Creating notification service: {self.config.notification_service.type}"
        )

        # Check if notification service is disabled
        if not self.config.notification_service.enabled:
            logger.info("Notification service is disabled, creating null notifier")
            return self._create_null_notifier()

        try:
            service_type = self.config.notification_service.type.lower()

            if service_type == "null":
                return self._create_null_notifier()
            elif service_type == "msteams":
                return self._create_msteams_notifier()
            elif service_type == "webhook":
                return self._create_webhook_notifier()
            elif service_type == "composite":
                # For future implementations - multiple notification providers
                return self._create_null_notifier()
            else:
                logger.warning(
                    f"Unknown notification service type: {self.config.notification_service.type}, using null notifier"
                )
                return self._create_null_notifier()

        except NotificationError:
            # Provide user-friendly guidance when notifications aren't configured
            logger.info(
                "📢 Notifications are disabled - configure notification settings to enable alerts"
            )
            return self._create_null_notifier()
        except Exception as e:
            # Handle other unexpected errors
            logger.warning(f"Failed to create notification service: {e}")
            return self._create_null_notifier()

    def _create_msteams_notifier(self) -> NotificationService:
        """Create MS Teams notifier with configuration."""
        config = self.config.notification_service.config

        return MSTeamsNotifier(
            webhook_url=config.get(
                "webhook_url"
            ),  # Can also be set via MSTEAMS_WEBHOOK_URL env var
            timeout_seconds=config.get("timeout_seconds", 30),
            max_retries=config.get("max_retries", 3),
            retry_delay=config.get("retry_delay", 1.0),
        )

    def _create_webhook_notifier(self) -> NotificationService:
        """Create generic webhook notifier with configuration."""
        config = self.config.notification_service.config

        return WebhookNotifier(
            webhook_url=config.get("webhook_url"),
            custom_headers=config.get("custom_headers", {}),
            timeout_seconds=config.get("timeout_seconds", 30),
            max_retries=config.get("max_retries", 3),
            retry_delay=config.get("retry_delay", 1.0),
        )

    def _create_null_notifier(self) -> NotificationService:
        """Create a null notifier that does nothing (for disabled notifications)."""
        return NullNotifier()

    def _create_null_registry(self) -> PackagesRegistryService:
        """Create a null registry with optional packages from configuration."""
        # Check if packages are configured for the null registry
        packages_data = self.config.packages_registry.config.get("packages", [])
        packages = self._convert_packages_data_to_entities(packages_data)
        return NullRegistry(packages=packages)

    def _convert_packages_data_to_entities(self, packages_data: list) -> list:
        """Convert packages data to MaliciousPackage entities.

        Supports both cases:
        - YAML dictionaries (from config files)
        - Already-created MaliciousPackage objects (from test fixtures)

        Args:
            packages_data: List of package data (dicts or MaliciousPackage objects)

        Returns:
            List of MaliciousPackage objects
        """
        from datetime import datetime, timezone

        packages = []
        for package_data in packages_data:
            # Case 1: Already a MaliciousPackage object (from test fixtures)
            if isinstance(package_data, MaliciousPackage):
                packages.append(package_data)
            # Case 2: Dictionary from YAML configuration
            elif isinstance(package_data, dict):
                # Convert string dates to datetime objects if needed
                published_at = package_data.get("published_at")
                if isinstance(published_at, str):
                    published_at = datetime.fromisoformat(
                        published_at.replace("Z", "+00:00")
                    )
                elif published_at is None:
                    published_at = datetime.now(timezone.utc)

                modified_at = package_data.get("modified_at")
                if isinstance(modified_at, str):
                    modified_at = datetime.fromisoformat(
                        modified_at.replace("Z", "+00:00")
                    )
                elif modified_at is None:
                    modified_at = datetime.now(timezone.utc)

                # Create MaliciousPackage from dictionary
                package = MaliciousPackage(
                    name=package_data.get("name", ""),
                    version=package_data.get("version", ""),
                    ecosystem=package_data.get("ecosystem", ""),
                    package_url=package_data.get("package_url", ""),
                    advisory_id=package_data.get("advisory_id", ""),
                    summary=package_data.get("summary", ""),
                    details=package_data.get("details", ""),
                    aliases=package_data.get("aliases", []),
                    affected_versions=package_data.get("affected_versions", []),
                    database_specific=package_data.get("database_specific", {}),
                    published_at=published_at,
                    modified_at=modified_at,
                )
                packages.append(package)
            else:
                logger.warning(f"Unknown package data type: {type(package_data)}")

        return packages

    def create_storage_service(self) -> StorageService:
        """
        Create storage service based on configuration.

        Returns:
            StorageService instance

        Raises:
            ServiceFactoryError: If provider creation fails
        """
        logger.debug(f"Creating storage service: {self.config.storage_service.type}")

        try:
            storage_type = self.config.storage_service.type.lower()

            if storage_type == "file":
                return FileStorage(
                    data_directory=self.config.storage_service.config.get(
                        "data_directory", "scan_results"
                    )
                )
            elif storage_type == "memory":
                return MemoryStorage(
                    max_scan_results=self.config.storage_service.config.get(
                        "max_scan_results", 1000
                    ),
                    clear_on_init=self.config.storage_service.config.get(
                        "clear_on_init", False
                    ),
                )
            elif storage_type == "database" or storage_type == "sqlite":
                db_path = self.config.storage_service.config.get(
                    "database_path", "data/security_scanner.db"
                )
                return DatabaseStorage(
                    database_path=db_path,
                    connection_timeout=self.config.storage_service.config.get(
                        "connection_timeout", 30.0
                    ),
                    max_connections=self.config.storage_service.config.get(
                        "max_connections", 10
                    ),
                    in_memory=True if db_path == ":memory:" else False,
                )
            else:
                raise ServiceFactoryError(
                    f"Unknown storage service type: {self.config.storage_service.type}"
                )

        except Exception as e:
            raise ServiceFactoryError(f"Failed to create storage service: {e}") from e
