from __future__ import annotations

import inspect
import logging

from app.core.exceptions import AppError
from app.modules.agent.engine.orchestrator.execution_context import ExecutionContext, GraphInvoker, GraphResolver, ProgressCallback
from app.modules.agent.engine.orchestrator.execution_engine import ExecutionEngine
from app.modules.agent.engine.orchestrator.models import OrchestratorResult, PlanStatus, TaskSpec
from app.modules.agent.engine.orchestrator.plan_compiler import PlanCompiler
from app.modules.agent.engine.orchestrator.plan_validator import PlanValidator
from app.modules.agent.engine.orchestrator.quality_gates import QualityGateRunner
from app.modules.agent.engine.orchestrator.result_assembler import ResultAssembler
from app.modules.agent.engine.orchestrator.step_registry import StepRegistry
from app.modules.agent.engine.orchestrator.template_registry import ScenarioTemplateRegistry
from app.schemas.common import ModuleName

LOGGER = logging.getLogger(__name__)


class OrchestratorService:
    def __init__(
        self,
        templates: ScenarioTemplateRegistry | None = None,
        compiler: PlanCompiler | None = None,
        validator: PlanValidator | None = None,
        step_registry: StepRegistry | None = None,
        gates: QualityGateRunner | None = None,
        engine: ExecutionEngine | None = None,
        assembler: ResultAssembler | None = None,
    ) -> None:
        self._templates = templates or ScenarioTemplateRegistry()
        self._compiler = compiler or PlanCompiler()
        self._validator = validator or PlanValidator()
        self._registry = step_registry or StepRegistry()
        self._gates = gates or QualityGateRunner()
        self._engine = engine or ExecutionEngine(self._registry, self._gates)
        self._assembler = assembler or ResultAssembler()

    async def run(
        self,
        *,
        task: TaskSpec,
        graph_resolver: GraphResolver,
        graph_invoker: GraphInvoker,
        progress_cb: ProgressCallback | None = None,
    ) -> OrchestratorResult:
        await self._emit_progress(progress_cb, "orchestrator.plan", "Building execution plan.")
        template = self._templates.build(task)
        plan = self._compiler.compile(template, task)

        errors = self._validator.validate(plan, task)
        if errors:
            raise AppError(
                code="invalid_execution_plan",
                desc=f"Execution plan validation failed: {'; '.join(errors)}",
                module=ModuleName.AGENT,
            )

        plan.status = PlanStatus.VALIDATED
        ctx = ExecutionContext(
            task=task,
            plan=plan,
            graph_resolver=graph_resolver,
            graph_invoker=graph_invoker,
            progress_cb=progress_cb,
        )

        await self._emit_progress(progress_cb, "orchestrator.run", "Executing plan steps.")
        step_results = await self._engine.run(ctx)
        if plan.status == PlanStatus.FAILED:
            errors = [f"{step.step_id}:{step.error_message or ','.join(step.warnings)}" for step in step_results if step.status.value != "success"]
            raise AppError(
                code="execution_plan_failed",
                desc=f"Execution plan failed: {'; '.join(errors)}",
                module=ModuleName.AGENT,
            )
        result = self._assembler.assemble(ctx, step_results)
        await self._emit_progress(progress_cb, "orchestrator.done", "Execution plan completed.")
        LOGGER.warning(
            "orchestrator decision: task_id=%s scenario=%s plan_status=%s steps=%s changeset_items=%s answer_len=%s",
            task.task_id,
            task.scenario.value,
            result.meta.get("plan", {}).get("status", ""),
            [
                {
                    "step_id": step.step_id,
                    "status": step.status.value,
                }
                for step in result.steps
            ],
            len(result.changeset),
            len(result.answer or ""),
        )
        return result

    async def _emit_progress(self, progress_cb: ProgressCallback | None, stage: str, message: str) -> None:
        if progress_cb is None:
            return
        result = progress_cb(stage, message, "task_progress", {"layer": "orchestrator"})
        if inspect.isawaitable(result):
            await result
