from __future__ import annotations

import json
from typing import Any

from armorer.agent import BaseAgent
from armorer.core.eval_models import VerificationResult, WorkflowPlan
from armorer.core.evaluators import run_verifier
from armorer.core.response import _normalize_core_response


def execute_bounded_workflow(
    *,
    task_id: str,
    prompt: str,
    verification_ledger: list[dict[str, Any]],
    tools: list[Any],
) -> dict[str, Any]:
    orchestrator = BaseAgent()
    orchestrator.init_agent(
        system_prompt=(
            "You are Armorer's workflow orchestrator. "
            "For bounded operational tasks, decide whether to answer directly or execute a short workflow. "
            "Prefer execution for installs, setup, configure, run, stop, and uninstall requests. "
            "Return only the structured workflow plan."
        ),
        session_id=f"armorer-core-{task_id}-workflow-orchestrator",
        agent_name="armorer:core:workflow-orchestrator",
    )
    plan = orchestrator.run(
        user_input=[{"role": "user", "content": prompt}],
        structured_output_model=WorkflowPlan,
    )
    if isinstance(plan, dict):
        plan = WorkflowPlan(**plan)
    if not isinstance(plan, WorkflowPlan):
        plan = WorkflowPlan(route="direct_answer", user_message=str(plan or "").strip())

    if plan.route == "direct_answer":
        return {"message": plan.user_message or "No action was needed."}

    executor = BaseAgent()
    executor.init_agent(
        system_prompt=(
            "You are Armorer's workflow executor. "
            "Carry out the bounded task using Armorer-managed tools first. "
            "If you start a background job, include a track_job ui_action and a non-empty message."
        ),
        session_id=f"armorer-core-{task_id}-workflow-executor",
        agent_name="armorer:core:workflow-executor",
        tools=tools,
    )
    executor_response = _normalize_core_response(
        executor.run(user_input=plan.task_brief or prompt)
    )

    try:
        verification = run_verifier(
            task_id=task_id,
            user_prompt=prompt,
            task_brief=plan.task_brief or prompt,
            acceptance_criteria=list(plan.acceptance_criteria or []),
            executor_response=executor_response,
            verification_ledger=verification_ledger,
        )
    except Exception:
        if (
            plan.execution_mode == "async"
            and isinstance(executor_response.get("ui_action"), dict)
            and str(executor_response["ui_action"].get("type") or "").strip().lower() == "track_job"
        ):
            return executor_response
        raise

    if verification.passed:
        payload = dict(executor_response)
        if str(verification.user_message or "").strip():
            payload["message"] = str(verification.user_message).strip()
        return payload

    if verification.retryable:
        retry_feedback = (
            f"Your previous response did not satisfy the task.\n"
            f"Failed criteria: {json.dumps(verification.failed_criteria)}\n"
            f"Evidence: {json.dumps(verification.evidence)}\n"
            "Fix the gaps and provide a concrete result."
        )
        executor_response = _normalize_core_response(
            executor.run(user_input=f"{plan.task_brief or prompt}\n\n{retry_feedback}")
        )
        verification = run_verifier(
            task_id=task_id,
            user_prompt=prompt,
            task_brief=plan.task_brief or prompt,
            acceptance_criteria=list(plan.acceptance_criteria or []),
            executor_response=executor_response,
            verification_ledger=verification_ledger,
        )
        if verification.passed:
            payload = dict(executor_response)
            if str(verification.user_message or "").strip():
                payload["message"] = str(verification.user_message).strip()
            return payload

    payload = dict(executor_response)
    if str(verification.user_message or "").strip():
        payload["message"] = str(verification.user_message).strip()
    return payload
