"""Shared mock fixtures for TDD plan-correct plan_id tests.

Provides constants, mock builders, and CLI argument helpers used by both
the Behave step definitions
(``features/steps/tdd_plan_correct_plan_id_steps.py``) and the Robot
Framework integration test helper
(``robot/helper_tdd_plan_correct_plan_id.py``).

Centralising the mock builders eliminates duplication and ensures both
test suites exercise the ``plan correct`` CLI path with identically-shaped
mock objects.

Bug: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/969
TDD: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/979
"""

from __future__ import annotations

from datetime import datetime
from types import SimpleNamespace
from unittest.mock import MagicMock

from cleveragents.domain.models.core.plan import (
    NamespacedName,
    Plan,
    PlanIdentity,
    PlanPhase,
    PlanTimestamps,
    ProcessingState,
    ProjectLink,
)

# ---------------------------------------------------------------------------
# Patch targets
# ---------------------------------------------------------------------------

PATCH_CONTAINER: str = "cleveragents.application.container.get_container"
PATCH_CORRECTION_SVC: str = (
    "cleveragents.application.services.correction_service.CorrectionService"
)
PATCH_RESOLVE_PLAN: str = "cleveragents.cli.commands.plan._resolve_active_plan_id"

# ---------------------------------------------------------------------------
# Fixed identifiers for deterministic assertions
# ---------------------------------------------------------------------------

# The plan_id is what the user passes as the first positional argument.
PLAN_ID: str = "01JBG969PLANID0000000000"
# The root_decision_id is what the code SHOULD resolve to when it detects
# the positional argument is a plan_id (not a decision_id).
ROOT_DECISION_ID: str = "DEC-ROOT-969"
# Used to build a multi-node decision tree for realism; not directly asserted.
CHILD_DECISION_ID: str = "DEC-CHILD-969"


# ---------------------------------------------------------------------------
# Mock builders
# ---------------------------------------------------------------------------


def make_decision_ns(
    decision_id: str,
    parent_decision_id: str | None,
) -> SimpleNamespace:
    """Create a minimal decision-like namespace for list_decisions."""
    return SimpleNamespace(
        decision_id=decision_id,
        parent_decision_id=parent_decision_id,
    )


def _make_plan(_plan_id: str) -> Plan:
    """Build a real ``Plan`` domain object so ``isinstance`` checks pass.

    The *_plan_id* argument is accepted for API symmetry but not used
    directly because ``PlanIdentity.plan_id`` must be a valid 26-char
    ULID.  A fresh ULID is generated instead.
    """
    from ulid import ULID

    return Plan(
        identity=PlanIdentity(plan_id=str(ULID())),
        namespaced_name=NamespacedName(namespace="local", name="tdd-969-plan"),
        action_name="local/tdd-969-action",
        description="TDD plan for bug #969",
        phase=PlanPhase.EXECUTE,
        processing_state=ProcessingState.COMPLETE,
        project_links=[ProjectLink(project_name="proj-1")],
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        timestamps=PlanTimestamps(created_at=datetime.now(), updated_at=datetime.now()),
    )


def make_mock_container(
    decisions: list[SimpleNamespace],
    influence_edges: dict[str, list[str]],
    correction_svc: MagicMock | None = None,
) -> MagicMock:
    """Build a mock DI container returning DecisionService and PlanLifecycleService."""
    mock_decision_svc = MagicMock()
    mock_decision_svc.list_decisions.return_value = decisions
    mock_decision_svc.get_influence_edges.return_value = influence_edges

    # The fix for bug #969 calls plan_lifecycle_service().get_plan()
    # to detect whether the identifier is a plan_id.  We must return
    # a real Plan so that ``isinstance(plan_obj, Plan)`` passes.
    mock_plan_svc = MagicMock()
    mock_plan_svc.get_plan.return_value = _make_plan(PLAN_ID)

    mock_container = MagicMock()
    mock_container.decision_service.return_value = mock_decision_svc
    mock_container.plan_lifecycle_service.return_value = mock_plan_svc
    if correction_svc is not None:
        mock_container.correction_service.return_value = correction_svc
    return mock_container


def make_correction_svc(
    target_decision_id: str,
    mode: str = "revert",
) -> MagicMock:
    """Build a mock CorrectionService that succeeds for a given target.

    Args:
        target_decision_id: The decision ID expected in the correction.
        mode: The correction mode (``"revert"`` or ``"append"``).
    """
    svc = MagicMock()
    svc.request_correction.return_value = SimpleNamespace(
        correction_id="CORR-TDD-969",
        mode=SimpleNamespace(value=mode),
        target_decision_id=target_decision_id,
        guidance="Use SQLAlchemy ORM instead of raw SQL",
    )
    svc.execute_correction.return_value = SimpleNamespace(
        correction_id="CORR-TDD-969",
        status=SimpleNamespace(value="applied"),
        reverted_decisions=[target_decision_id],
        new_decisions=[],
    )
    return svc


def make_default_decisions() -> list[SimpleNamespace]:
    """Build the standard two-node decision tree (root + child)."""
    return [
        make_decision_ns(ROOT_DECISION_ID, None),
        make_decision_ns(CHILD_DECISION_ID, ROOT_DECISION_ID),
    ]


def make_default_container() -> MagicMock:
    """Build a mock container with the standard decision tree."""
    return make_mock_container(make_default_decisions(), {})


def build_cli_args(plan_id: str, mode: str = "revert") -> list[str]:
    """Build the CLI argument list for ``plan correct <plan_id>``.

    Args:
        plan_id: The plan ID to pass as the first positional argument.
        mode: The correction mode (``"revert"`` or ``"append"``).
    """
    return [
        "correct",
        plan_id,
        "--mode",
        mode,
        "--guidance",
        "Use SQLAlchemy ORM instead of raw SQL",
        "--yes",
        "--format",
        "plain",
    ]


__all__: list[str] = [
    "CHILD_DECISION_ID",
    "PATCH_CONTAINER",
    "PATCH_CORRECTION_SVC",
    "PATCH_RESOLVE_PLAN",
    "PLAN_ID",
    "ROOT_DECISION_ID",
    "build_cli_args",
    "make_correction_svc",
    "make_decision_ns",
    "make_default_container",
    "make_default_decisions",
    "make_mock_container",
]
