"""
PipelineFactory - Discover pipelines from a config root and create ETLOrchestrators.

Each subdirectory under config_root that contains extract.yaml and load.yaml
(and optionally transform.yaml) is treated as one pipeline.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from pycharter.etl_generator.orchestrator import ETLOrchestrator

# Default required files for a pipeline directory
DEFAULT_REQUIRED_FILES = ("extract.yaml", "load.yaml")


class PipelineFactory:
    """
    Discover pipelines from a root directory and create ETLOrchestrator instances.
    """

    def __init__(
        self,
        config_root: str = "configs",
        excluded_dirs: list[str] | None = None,
        required_files: tuple | None = None,
    ):
        """
        Args:
            config_root: Root directory to scan for pipeline subdirs.
            excluded_dirs: Directory names to skip (e.g. "templates", "__pycache__").
            required_files: Tuple of required file names (default: extract.yaml, load.yaml).
        """
        self.config_root = Path(config_root).resolve()
        self.excluded_dirs = set(
            excluded_dirs if excluded_dirs is not None else ["templates"]
        )
        self.required_files = required_files or DEFAULT_REQUIRED_FILES
        self._pipeline_dirs: dict[str, str] | None = None

    def refresh(self) -> None:
        """Rescan config_root and rebuild the pipeline list."""
        self._pipeline_dirs = None

    def _scan(self) -> dict[str, str | None]:
        """Scan config_root for pipeline directories; return name -> absolute path."""
        if self._pipeline_dirs is not None:
            return self._pipeline_dirs
        if not self.config_root.is_dir():
            self._pipeline_dirs = {}
            return self._pipeline_dirs

        out: dict[str, str | None] = {}
        for path in self.config_root.iterdir():
            if not path.is_dir():
                continue
            if (
                path.name in self.excluded_dirs
                or path.name.startswith(".")
                or path.name.startswith("_")
            ):
                continue
            # Check for nested pipeline dirs (e.g. configs/fmp/fmp_biggest_gainers)
            for sub in path.iterdir():
                if not sub.is_dir():
                    continue
                if (
                    sub.name in self.excluded_dirs
                    or sub.name.startswith(".")
                    or sub.name.startswith("_")
                ):
                    continue
                if all((sub / f).exists() for f in self.required_files):
                    out[sub.name] = str(sub.resolve())
            # Also treat direct child as pipeline if it has required files
            if all((path / f).exists() for f in self.required_files):
                out[path.name] = str(path.resolve())
        self._pipeline_dirs = out
        return out

    def get_pipeline_names(self) -> list[str]:
        """Return sorted list of discovered pipeline names."""
        return sorted(self._scan().keys())

    def get_contract_dir(self, pipeline_name: str) -> str | None:
        """Return absolute path to the pipeline's contract directory, or None."""
        return self._scan().get(pipeline_name)

    def create_orchestrator(
        self,
        pipeline_name: str,
        config_context: dict[str, Any] | None = None,
        verbose: bool = True,
        **orchestrator_kwargs: Any,
    ) -> ETLOrchestrator:
        """
        Create an ETLOrchestrator for the given pipeline name.

        Args:
            pipeline_name: Name from get_pipeline_names().
            config_context: Variables for ${VAR} substitution.
            verbose: Passed to ETLOrchestrator.
            **orchestrator_kwargs: Passed to ETLOrchestrator constructor.

        Returns:
            ETLOrchestrator instance.

        Raises:
            ValueError: If pipeline_name is not found.
        """
        contract_dir = self.get_contract_dir(pipeline_name)
        if not contract_dir:
            available = ", ".join(self.get_pipeline_names())
            raise ValueError(
                f"Pipeline '{pipeline_name}' not found. Available: {available}"
            )
        return ETLOrchestrator(
            contract_dir=contract_dir,
            config_context=config_context,
            verbose=verbose,
            **orchestrator_kwargs,
        )
