"""This module implements Kedro boot session. A user facing interface responsible for orchestring the interaction between kedro and the Apps."""

import logging
import uuid
from typing import Any, List, Optional

from kedro.config import OmegaConfigLoader
from kedro.io import DataCatalog
from pluggy import PluginManager
from kedro.pipeline.pipeline import Pipeline

from kedro_boot.framework.compiler.specs import CompilationSpec

from kedro_boot.framework.context import KedroBootContext
from .runner import KedroBootRunner

LOGGER = logging.getLogger(__name__)


class KedroBootSession:
    """``KedroBootSession`` A user facing interface that expose kedro's resource to the kedro boot apps.
    At init time : It create the ``KedroBootContext`` and a ``KedroBootRunner``
    At compilation time : It compile the catalog through the context. This makes the catalog ready for the run iterations.
    At iteration time: It render a new catalog with app data (through the context) and run the pipeline
    """

    def __init__(
        self,
        pipeline: Pipeline,
        catalog: DataCatalog,
        hook_manager: PluginManager,
        session_id: str,
        app_runtime_params: dict,
        config_loader: OmegaConfigLoader,
    ) -> None:
        """Init the kedro boot session.

        Args:
            pipeline (Pipeline): ``AppPipeline`` object
            catalog (DataCatalog): kedro ``DataCatalog`` object
            hook_manager (PluginManager): kedro  ``PluginManager`` object
            session_id (str): kedro ``KedroSession`` session_id
            app_runtime_params (dict): params given by an App specific CLI
            config_loader (OmegaConfigLoader): kedro ``OmegaConfigLoader`` object
        """
        self._context = KedroBootContext(pipeline=pipeline, catalog=catalog)
        self._runner = KedroBootRunner(hook_manager=hook_manager, session_id=session_id)

        self.app_runtime_params = app_runtime_params
        self.config_loader = config_loader

        self._is_catalog_compiled = False

    def compile(self, compilation_specs: List[CompilationSpec] = None) -> None:
        """Prepare the Catalog for iteration time. The goal is to achieve low latency by minimizing operations needed during the run of an iteration.
        A pipeline view provides a perspective on the underlying pipeline, filtered by a particular tag and organized by datasets categories according to their relevance to the external application.
        The compilation is triggered automatically by the kedro boot. To give the app the control of the compilation point, set the AbstractKedroBootApp class attribute 'LAZY_COMPILE' to True. If the compilation is neither triggered by the kedro project nor the app, it will be triggered lazily during the first run iteration.


        Raises:
            KedroBootSessionError: _description_
        """
        if self._is_catalog_compiled:
            LOGGER.warning("The session is already compiled")
        else:
            self._context.compile(compilation_specs=compilation_specs)
            self._is_catalog_compiled = True

    def run(
        self,
        namespace: Optional[str] = None,
        inputs: Optional[dict] = None,
        parameters: Optional[dict] = None,
        itertime_params: Optional[dict] = None,
        run_id: Optional[str] = None,
    ) -> Any:
        """Perform a low-latency run of a pipeline's namespace using the provided inputs, parameters and itertime_params.

        Args:
            namespace (str): pipeline's namespace.
            inputs (dict): App inputs datasets that will be injected into the catalog.
            parameters (dict): App parameters datasets that will be injected into the catalog.
            itertime_params (dict): App itertime params that will resolve the itertime_params resolvers.
            run_id (str): run_id can be generated by the app, otherwise the session generate it at each iteration.

        Raises:
            KedroBootSessionError: _description_

        Returns:
            Any: Run results
        """
        # pipeline_view_name = name or DEFAULT_PIPELINE_VIEW_NAME
        iteration_run_id = run_id or uuid.uuid4().hex
        itertime_params = itertime_params or {}
        iteration_template_params = {**itertime_params, **{"run_id": iteration_run_id}}

        LOGGER.info(f"Running iteration {iteration_run_id}")
        # Coompile catalog lazily at first iteration, if it is not already compiled earlier by the app
        if not self._is_catalog_compiled:
            LOGGER.warning(
                "Lazy Catalog compilation at first iteration run. Beware, since no compilation specs given, the compilation specs are infered from the pipeline."
            )
            self.compile()

        pipeline, catalog, outputs_datasets = self._context.render(
            namespace=namespace,
            inputs=inputs,
            parameters=parameters,
            itertime_params=iteration_template_params,
        )

        iteration_outputs = self._runner.run(
            pipeline=pipeline,
            catalog=catalog,
            outputs_datasets=outputs_datasets,
            run_id=iteration_run_id,
        )

        LOGGER.info(f"Iteration {iteration_run_id} completed")

        return iteration_outputs

    def get_credentials(self) -> dict:
        return self.config_loader["credentials"]


class KedroBootSessionError(Exception):
    """Error raised in case of kedro boot session error"""
