"""
Pipeline Registry - Database-backed discovery and management of pipelines.

The registry maintains a list of registered pipelines and their metadata,
allowing the system to discover available pipelines and instantiate them.
"""

from __future__ import annotations

import importlib
import logging
from datetime import datetime
from typing import TYPE_CHECKING, Any

from pipeline_core.contracts import PipelineDetail, PipelineInfo

if TYPE_CHECKING:
    import asyncpg

    from pipeline_core.base import BasePipeline

logger = logging.getLogger(__name__)


class PipelineRegistry:
    """
    Database-backed registry for pipeline discovery and management.

    The registry stores pipeline metadata in a PostgreSQL table and provides
    methods to register, list, and instantiate pipelines.

    Usage:
        pool = await asyncpg.create_pool(database_url)
        registry = PipelineRegistry(pool)

        # Register a pipeline
        await registry.register(
            pipeline_id="reviewiq",
            name="ReviewIQ Pipeline",
            description="Classifies reviews",
            version="1.0.0",
            module_path="reviewiq_pipeline.pipeline:ReviewIQPipeline",
        )

        # List pipelines
        pipelines = await registry.list_pipelines()

        # Get a pipeline instance
        pipeline = await registry.get_pipeline("reviewiq")
    """

    def __init__(self, pool: asyncpg.Pool):
        """
        Initialize the registry.

        Args:
            pool: asyncpg connection pool
        """
        self._pool = pool
        self._instances: dict[str, BasePipeline] = {}

    async def register(
        self,
        pipeline_id: str,
        name: str,
        description: str,
        version: str,
        module_path: str,
        stages: list[str],
        input_type: str,
        config: dict[str, Any] | None = None,
        is_enabled: bool = True,
    ) -> None:
        """
        Register a pipeline in the database.

        Args:
            pipeline_id: Unique pipeline identifier
            name: Display name
            description: Human-readable description
            version: Semantic version
            module_path: Python module path (e.g., "package.module:ClassName")
            stages: List of stage names
            input_type: Expected input type
            config: Optional pipeline configuration
            is_enabled: Whether the pipeline is enabled

        Raises:
            ValueError: If module_path is invalid
        """
        # Validate module path format
        if ":" not in module_path:
            raise ValueError(
                f"Invalid module_path: {module_path}. "
                "Expected format: 'package.module:ClassName'"
            )

        async with self._pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO pipeline.registry (
                    pipeline_id, name, description, version, module_path,
                    stages, input_type, config, is_enabled, updated_at
                )
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
                ON CONFLICT (pipeline_id) DO UPDATE SET
                    name = EXCLUDED.name,
                    description = EXCLUDED.description,
                    version = EXCLUDED.version,
                    module_path = EXCLUDED.module_path,
                    stages = EXCLUDED.stages,
                    input_type = EXCLUDED.input_type,
                    config = EXCLUDED.config,
                    is_enabled = EXCLUDED.is_enabled,
                    updated_at = NOW()
                """,
                pipeline_id,
                name,
                description,
                version,
                module_path,
                stages,
                input_type,
                config,
                is_enabled,
            )

        logger.info(f"Registered pipeline: {pipeline_id} v{version}")

    async def register_from_instance(
        self,
        pipeline: BasePipeline,
        module_path: str,
        config: dict[str, Any] | None = None,
    ) -> None:
        """
        Register a pipeline from an instance.

        Args:
            pipeline: Pipeline instance
            module_path: Python module path
            config: Optional configuration
        """
        metadata = pipeline.metadata
        await self.register(
            pipeline_id=metadata["id"],
            name=metadata["name"],
            description=metadata["description"],
            version=metadata["version"],
            module_path=module_path,
            stages=metadata["stages"],
            input_type=metadata["input_type"],
            config=config,
        )
        # Cache the instance
        self._instances[metadata["id"]] = pipeline

    async def unregister(self, pipeline_id: str) -> bool:
        """
        Unregister a pipeline from the database.

        Args:
            pipeline_id: Pipeline identifier to remove

        Returns:
            True if pipeline was removed, False if not found
        """
        async with self._pool.acquire() as conn:
            result = await conn.execute(
                "DELETE FROM pipeline.registry WHERE pipeline_id = $1",
                pipeline_id,
            )

        # Remove from cache
        self._instances.pop(pipeline_id, None)

        deleted = result.split()[-1] != "0"
        if deleted:
            logger.info(f"Unregistered pipeline: {pipeline_id}")
        return deleted

    async def set_enabled(self, pipeline_id: str, enabled: bool) -> bool:
        """
        Enable or disable a pipeline.

        Args:
            pipeline_id: Pipeline identifier
            enabled: Whether to enable or disable

        Returns:
            True if pipeline was updated, False if not found
        """
        async with self._pool.acquire() as conn:
            result = await conn.execute(
                """
                UPDATE pipeline.registry
                SET is_enabled = $2, updated_at = NOW()
                WHERE pipeline_id = $1
                """,
                pipeline_id,
                enabled,
            )

        updated = result.split()[-1] != "0"
        if updated:
            logger.info(f"Set pipeline {pipeline_id} enabled={enabled}")
        return updated

    async def list_pipelines(
        self,
        enabled_only: bool = True,
    ) -> list[PipelineInfo]:
        """
        List all registered pipelines.

        Args:
            enabled_only: Only return enabled pipelines

        Returns:
            List of PipelineInfo dictionaries
        """
        async with self._pool.acquire() as conn:
            if enabled_only:
                rows = await conn.fetch(
                    """
                    SELECT pipeline_id, name, description, version,
                           is_enabled, stages, input_type
                    FROM pipeline.registry
                    WHERE is_enabled = TRUE
                    ORDER BY name
                    """
                )
            else:
                rows = await conn.fetch(
                    """
                    SELECT pipeline_id, name, description, version,
                           is_enabled, stages, input_type
                    FROM pipeline.registry
                    ORDER BY name
                    """
                )

        return [
            PipelineInfo(
                id=row["pipeline_id"],
                name=row["name"],
                description=row["description"],
                version=row["version"],
                is_enabled=row["is_enabled"],
                stages=row["stages"],
                input_type=row["input_type"],
            )
            for row in rows
        ]

    async def get_pipeline_detail(
        self,
        pipeline_id: str,
    ) -> PipelineDetail | None:
        """
        Get detailed information about a pipeline.

        Args:
            pipeline_id: Pipeline identifier

        Returns:
            PipelineDetail or None if not found
        """
        async with self._pool.acquire() as conn:
            row = await conn.fetchrow(
                """
                SELECT pipeline_id, name, description, version, module_path,
                       is_enabled, stages, input_type, config,
                       created_at, updated_at
                FROM pipeline.registry
                WHERE pipeline_id = $1
                """,
                pipeline_id,
            )

        if not row:
            return None

        return PipelineDetail(
            id=row["pipeline_id"],
            name=row["name"],
            description=row["description"],
            version=row["version"],
            is_enabled=row["is_enabled"],
            stages=row["stages"],
            input_type=row["input_type"],
            module_path=row["module_path"],
            config=row["config"],
            created_at=row["created_at"].isoformat() if row["created_at"] else None,
            updated_at=row["updated_at"].isoformat() if row["updated_at"] else None,
        )

    async def get_pipeline(
        self,
        pipeline_id: str,
        initialize: bool = True,
    ) -> BasePipeline | None:
        """
        Get a pipeline instance.

        Args:
            pipeline_id: Pipeline identifier
            initialize: Whether to call initialize() on the pipeline

        Returns:
            Pipeline instance or None if not found

        This method caches pipeline instances for reuse.
        """
        # Check cache first
        if pipeline_id in self._instances:
            return self._instances[pipeline_id]

        # Get pipeline details from database
        detail = await self.get_pipeline_detail(pipeline_id)
        if not detail or not detail["is_enabled"]:
            return None

        # Import and instantiate the pipeline
        try:
            pipeline = self._import_pipeline(detail["module_path"])
        except Exception as e:
            logger.error(f"Failed to import pipeline {pipeline_id}: {e}")
            return None

        # Initialize if requested
        if initialize:
            try:
                await pipeline.initialize()
            except Exception as e:
                logger.error(f"Failed to initialize pipeline {pipeline_id}: {e}")
                return None

        # Cache and return
        self._instances[pipeline_id] = pipeline
        return pipeline

    def _import_pipeline(self, module_path: str) -> BasePipeline:
        """
        Import a pipeline class from a module path.

        Args:
            module_path: Path in format "package.module:ClassName"

        Returns:
            Pipeline instance
        """
        module_name, class_name = module_path.rsplit(":", 1)
        module = importlib.import_module(module_name)
        cls = getattr(module, class_name)
        return cls()

    async def close_all(self) -> None:
        """Close all cached pipeline instances."""
        for pipeline_id, pipeline in self._instances.items():
            try:
                await pipeline.close()
            except Exception as e:
                logger.error(f"Error closing pipeline {pipeline_id}: {e}")

        self._instances.clear()


class InMemoryPipelineRegistry:
    """
    In-memory pipeline registry for testing and simple deployments.

    This registry doesn't persist to a database and stores pipelines in memory.
    """

    def __init__(self):
        self._pipelines: dict[str, BasePipeline] = {}
        self._enabled: dict[str, bool] = {}

    async def register(self, pipeline: BasePipeline) -> None:
        """Register a pipeline instance."""
        pipeline_id = pipeline.metadata["id"]
        self._pipelines[pipeline_id] = pipeline
        self._enabled[pipeline_id] = True
        logger.info(f"Registered pipeline: {pipeline_id}")

    async def unregister(self, pipeline_id: str) -> bool:
        """Unregister a pipeline."""
        if pipeline_id in self._pipelines:
            del self._pipelines[pipeline_id]
            del self._enabled[pipeline_id]
            return True
        return False

    async def set_enabled(self, pipeline_id: str, enabled: bool) -> bool:
        """Enable or disable a pipeline."""
        if pipeline_id in self._enabled:
            self._enabled[pipeline_id] = enabled
            return True
        return False

    async def list_pipelines(
        self,
        enabled_only: bool = True,
    ) -> list[PipelineInfo]:
        """List all registered pipelines."""
        result = []
        for pipeline_id, pipeline in self._pipelines.items():
            is_enabled = self._enabled.get(pipeline_id, True)
            if enabled_only and not is_enabled:
                continue

            metadata = pipeline.metadata
            result.append(
                PipelineInfo(
                    id=pipeline_id,
                    name=metadata["name"],
                    description=metadata["description"],
                    version=metadata["version"],
                    is_enabled=is_enabled,
                    stages=metadata["stages"],
                    input_type=metadata["input_type"],
                )
            )
        return result

    async def get_pipeline(
        self,
        pipeline_id: str,
        initialize: bool = True,
    ) -> BasePipeline | None:
        """Get a pipeline instance."""
        if pipeline_id not in self._pipelines:
            return None

        if not self._enabled.get(pipeline_id, True):
            return None

        pipeline = self._pipelines[pipeline_id]

        if initialize:
            await pipeline.initialize()

        return pipeline

    async def close_all(self) -> None:
        """Close all pipeline instances."""
        for pipeline in self._pipelines.values():
            try:
                await pipeline.close()
            except Exception as e:
                logger.error(f"Error closing pipeline: {e}")

        self._pipelines.clear()
        self._enabled.clear()
