"""Transport-neutral preparation and runtime selection for ABI workflows."""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Mapping, MutableMapping, Sequence

from abi.interfaces import ABIPublishedOutputsPlugin
from abi.runtimes import (
    HpcRuntime,
    LocalRuntime,
    NextflowRuntime,
    RuntimeOptions,
    RuntimeResult,
    SnakemakeRuntime,
)
from abi.schemas import ABIError


@dataclass(frozen=True)
class PreparedWorkflow:
    """A resolved plugin config and canonical plan ready for one runtime."""

    analysis_type: str
    config: Mapping[str, Any]
    plan: Any
    options: RuntimeOptions
    plugin: Any = field(repr=False)


class WorkflowCoordinator:
    """Prepare and execute ABI workflows behind one transport-neutral interface."""

    def prepare(
        self,
        analysis_type: str,
        config_path: str | Path | None = None,
        *,
        profile: str | None = None,
        db_profile: str | None = None,
        overrides: Mapping[str, Any] | None = None,
        resource_overrides: Sequence[str] = (),
        check_files: bool = True,
        options: RuntimeOptions | None = None,
    ) -> PreparedWorkflow:
        from abi.plugins import get_plugin

        plugin = get_plugin(analysis_type)
        config = plugin.load_config(
            Path(config_path) if config_path is not None else None,
            profile=profile,
            db_profile=db_profile,
            overrides=overrides,
        )
        if resource_overrides:
            from abi.resources import apply_resource_overrides

            apply_resource_overrides(config, list(resource_overrides))
        plan = plugin.build_plan(config, check_files=check_files)
        return PreparedWorkflow(
            analysis_type=analysis_type,
            config=config,
            plan=plan,
            options=options or RuntimeOptions(engine="local"),
            plugin=plugin,
        )

    def dry_run(self, prepared: PreparedWorkflow) -> RuntimeResult:
        runtime = self._runtime(prepared)
        execute_dry_run = getattr(prepared.plugin, "execute_dry_run", None)
        if prepared.options.engine.lower().strip() == "local" and callable(execute_dry_run):
            outputs = execute_dry_run(prepared.plan, prepared.config)
            return RuntimeResult(status="success", return_code=0, outputs=dict(outputs))
        return runtime.dry_run(prepared.plan, prepared.config)

    def run(self, prepared: PreparedWorkflow) -> RuntimeResult:
        result = self._runtime(prepared).run(prepared.plan, prepared.config)
        self.merge_published_outputs(prepared, result.outputs)
        return result

    @staticmethod
    def merge_published_outputs(
        prepared: PreparedWorkflow, outputs: MutableMapping[str, Path]
    ) -> None:
        """Add a plugin's stable final artifacts to a fresh or resumed ABI result."""
        if isinstance(prepared.plugin, ABIPublishedOutputsPlugin):
            published = {
                str(name): Path(path)
                for name, path in prepared.plugin.published_outputs(prepared.plan).items()
            }
            collisions = sorted(set(outputs) & set(published))
            if collisions:
                raise ABIError(
                    "Plugin published output label(s) collide with ABI outputs: "
                    + ", ".join(collisions)
                )
            outputs.update(published)

    @staticmethod
    def _runtime(prepared: PreparedWorkflow) -> Any:
        engine = prepared.options.engine.lower().strip()
        if engine == "local":
            return LocalRuntime(prepared.plugin, options=prepared.options)
        if engine == "nextflow":
            return NextflowRuntime(prepared.plugin, options=prepared.options)
        if engine == "snakemake":
            return SnakemakeRuntime(prepared.plugin, options=prepared.options)
        if engine == "hpc":
            return HpcRuntime(prepared.plugin, options=prepared.options)
        raise ABIError(
            f"Unsupported runtime engine: {prepared.options.engine}. "
            "Expected local, nextflow, snakemake, or hpc."
        )
