"""Helper script for wf14_server_mode_integration.robot tests.

Each subcommand is a self-contained check that prints a sentinel on success.
Validates the server mode team collaboration workflow (Specification
Example 14) with mocked LLM providers and in-memory domain services.
"""

from __future__ import annotations

import logging
import os
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path

# Keep helper stderr focused on assertion failures and tracebacks.
logging.disable(logging.CRITICAL)

# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from cleveragents.application.services.config_service import (  # noqa: E402
    _REGISTRY,
    ConfigLevel,
    ConfigService,
)
from cleveragents.application.services.plan_lifecycle_service import (  # noqa: E402
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings  # noqa: E402
from cleveragents.domain.models.core.action import (  # noqa: E402
    ActionArgument,
    ActionState,
)
from cleveragents.domain.models.core.plan import (  # noqa: E402
    PlanPhase,
    ProcessingState,
    ProjectLink,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_config_service(tmpdir: str) -> ConfigService:
    """Create a ConfigService backed by a temporary directory."""
    config_dir = Path(tmpdir)
    config_path = config_dir / "config.toml"
    return ConfigService(config_dir=config_dir, config_path=config_path)


_WF14_CONFIG_ENV_OVERRIDES: tuple[str, ...] = (
    "CLEVERAGENTS_SERVER_URL",
    "CLEVERAGENTS_SERVER_TOKEN",
    "CLEVERAGENTS_SERVER_SYNC_AUTO",
    "CLEVERAGENTS_SERVER_SYNC_INTERVAL",
    "CLEVERAGENTS_SERVER_TLS_VERIFY",
    "CLEVERAGENTS_SERVER_NAMESPACE",
    "CLEVERAGENTS_CORE_NAMESPACE",
    "CLEVERAGENTS_NAMESPACE",
    "CLEVERAGENTS_CORE_AUTOMATION_PROFILE",
    "CLEVERAGENTS_AUTOMATION_PROFILE",
)


def _clear_config_env_overrides() -> None:
    """Remove env vars that can override config resolution in tests."""
    for env_name in _WF14_CONFIG_ENV_OVERRIDES:
        os.environ.pop(env_name, None)


# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------


def config_server_mode() -> None:
    """Set and verify server config values using ConfigService."""
    _clear_config_env_overrides()

    with tempfile.TemporaryDirectory() as tmpdir:
        svc = _make_config_service(tmpdir)

        # Set server mode config values
        svc.set_value("server.url", "https://agents.example.com")
        svc.set_value("server.token", "tok_wf14_test_abc123")
        svc.set_value("core.namespace", "team-alpha")

        # Read back and verify
        data = svc.read_config()
        assert data["server.url"] == "https://agents.example.com", (
            "Expected server.url='https://agents.example.com', "
            f"got {data.get('server.url')}"
        )
        assert data["server.token"] == "tok_wf14_test_abc123", (
            "Expected server.token='tok_wf14_test_abc123', "
            f"got {data.get('server.token')}"
        )
        assert data["core.namespace"] == "team-alpha", (
            f"Expected core.namespace='team-alpha', got {data.get('core.namespace')}"
        )

        # Verify resolution chain returns correct values
        resolved_url = svc.resolve("server.url")
        assert resolved_url.value == "https://agents.example.com", (
            "Expected resolve(server.url) to return configured URL, "
            f"got {resolved_url.value!r}"
        )
        assert resolved_url.source == ConfigLevel.GLOBAL, (
            "Expected resolve(server.url) source=global, "
            f"got {resolved_url.source.value}"
        )

        resolved_token = svc.resolve("server.token")
        assert resolved_token.value == "tok_wf14_test_abc123", (
            "Expected resolve(server.token) to return configured token, "
            f"got {resolved_token.value!r}"
        )
        assert resolved_token.source == ConfigLevel.GLOBAL, (
            "Expected resolve(server.token) source=global, "
            f"got {resolved_token.source.value}"
        )

        resolved_ns = svc.resolve("core.namespace")
        assert resolved_ns.value == "team-alpha", (
            "Expected resolve(core.namespace) to return configured namespace, "
            f"got {resolved_ns.value!r}"
        )
        assert resolved_ns.source == ConfigLevel.GLOBAL, (
            "Expected resolve(core.namespace) source=global, "
            f"got {resolved_ns.source.value}"
        )

    print("wf14-config-server-mode-ok")


def diagnostics() -> None:
    """Verify config registry contains required server-mode keys and defaults.

    Checks that all server-mode configuration keys are registered and that
    their default values match the specification.  Server connectivity and
    database checks are not exercised here because no real server or
    database is available in the integration test environment.
    """
    _clear_config_env_overrides()

    # Verify config registry has required server keys
    required_keys = [
        "server.url",
        "server.token",
        "server.sync.auto",
        "server.sync.interval",
        "server.tls-verify",
        "server.namespace",
        "core.namespace",
        "core.automation-profile",
    ]
    for key in required_keys:
        assert key in _REGISTRY, f"Missing config key: {key}"

    # Verify config service can be instantiated with temp directory
    with tempfile.TemporaryDirectory() as tmpdir:
        svc = _make_config_service(tmpdir)

        # Verify basic config reads succeed (empty config initially)
        data = svc.read_config()
        assert len(data) == 0, (
            "Expected empty initial config in temporary directory, "
            f"got keys: {sorted(data.keys())}"
        )

        # Verify default values are accessible via resolution
        resolved = svc.resolve("server.sync.auto")
        assert resolved.value is True, f"Expected True, got {resolved.value}"
        assert resolved.source == ConfigLevel.DEFAULT, (
            f"Expected server.sync.auto source=default, got {resolved.source.value}"
        )

        resolved_interval = svc.resolve("server.sync.interval")
        assert resolved_interval.value == 300, (
            f"Expected 300, got {resolved_interval.value}"
        )
        assert resolved_interval.source == ConfigLevel.DEFAULT, (
            "Expected server.sync.interval source=default, "
            f"got {resolved_interval.source.value}"
        )

        resolved_tls = svc.resolve("server.tls-verify")
        assert resolved_tls.value is True, f"Expected True, got {resolved_tls.value}"
        assert resolved_tls.source == ConfigLevel.DEFAULT, (
            "Expected server.tls-verify source=default, "
            f"got {resolved_tls.source.value}"
        )

        # Verify core.namespace default is 'local'
        resolved_ns = svc.resolve("core.namespace")
        assert resolved_ns.value == "local", (
            f"Expected 'local', got {resolved_ns.value}"
        )
        assert resolved_ns.source == ConfigLevel.DEFAULT, (
            f"Expected core.namespace source=default, got {resolved_ns.source.value}"
        )

        # Verify automation-profile default is 'supervised'
        resolved_profile = svc.resolve("core.automation-profile")
        assert resolved_profile.value == "supervised", (
            f"Expected 'supervised', got {resolved_profile.value}"
        )
        assert resolved_profile.source == ConfigLevel.DEFAULT, (
            "Expected core.automation-profile source=default, "
            f"got {resolved_profile.source.value}"
        )

        # Server connectivity cannot be tested (no real server) -- verify
        # that the server.url default is None (graceful handling)
        resolved_url = svc.resolve("server.url")
        assert resolved_url.value is None, (
            f"Expected None default for server.url, got {resolved_url.value}"
        )
        assert resolved_url.source == ConfigLevel.DEFAULT, (
            f"Expected server.url source=default, got {resolved_url.source.value}"
        )

    print("wf14-diagnostics-ok")


def action_namespace() -> None:
    """Create and list namespaced actions using PlanLifecycleService."""
    settings = Settings()
    lifecycle = PlanLifecycleService(settings=settings)
    definition_of_done = "Service is running and health checks pass"

    # Create an action with a namespace prefix
    action = lifecycle.create_action(
        name="team-alpha/deploy-service",
        description="Deploy a microservice to staging",
        definition_of_done=definition_of_done,
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
        tags=["deploy", "team-alpha"],
    )

    assert str(action.namespaced_name) == "team-alpha/deploy-service", (
        f"Expected 'team-alpha/deploy-service', got '{action.namespaced_name}'"
    )
    assert action.state == ActionState.AVAILABLE, "Expected action state AVAILABLE"
    assert action.reusable is True, "Expected action.reusable to default to True"
    assert action.read_only is False, "Expected action.read_only to default to False"
    assert action.automation_profile == "supervised", (
        "Expected action automation_profile='supervised', "
        f"got {action.automation_profile!r}"
    )
    assert action.definition_of_done == definition_of_done, (
        "Expected definition_of_done to match the configured value"
    )
    assert action.tags == ["deploy", "team-alpha"], (
        f"Expected action tags ['deploy', 'team-alpha'], got {action.tags!r}"
    )

    # List actions and verify the namespaced action appears
    actions = lifecycle.list_actions()
    assert len(actions) == 1, f"Expected 1 action, got {len(actions)}"
    action_names = [str(a.namespaced_name) for a in actions]
    assert "team-alpha/deploy-service" in action_names, (
        f"'team-alpha/deploy-service' not in {action_names}"
    )

    print("wf14-action-namespace-ok")


def action_actor_refs() -> None:
    """Create an action with namespaced actor references and verify fields.

    Validates that actions can reference actors from different namespaces
    (strategy, execution, review) and that those references are preserved
    when the action is retrieved.
    """
    settings = Settings()
    lifecycle = PlanLifecycleService(settings=settings)

    # Create an action that references namespaced actors
    action = lifecycle.create_action(
        name="team-alpha/analyze-code",
        description="Analyze codebase for improvements",
        definition_of_done="Analysis report generated",
        strategy_actor="openai/gpt-4",
        execution_actor="anthropic/claude-sonnet",
        review_actor="openai/gpt-4",
        automation_profile="supervised",
    )

    assert action.strategy_actor == "openai/gpt-4", (
        f"Expected strategy_actor 'openai/gpt-4', got {action.strategy_actor!r}"
    )
    assert action.execution_actor == "anthropic/claude-sonnet", (
        "Expected execution_actor 'anthropic/claude-sonnet', "
        f"got {action.execution_actor!r}"
    )
    assert action.review_actor == "openai/gpt-4", (
        f"Expected review_actor 'openai/gpt-4', got {action.review_actor!r}"
    )
    assert action.automation_profile == "supervised", (
        "Expected action automation_profile='supervised', "
        f"got {action.automation_profile!r}"
    )

    # Verify the action is accessible
    fetched = lifecycle.get_action("team-alpha/analyze-code")
    assert str(fetched.namespaced_name) == "team-alpha/analyze-code", (
        "Expected fetched action name 'team-alpha/analyze-code', "
        f"got {fetched.namespaced_name!s}"
    )
    assert fetched.strategy_actor == "openai/gpt-4", (
        "Expected fetched strategy_actor 'openai/gpt-4', "
        f"got {fetched.strategy_actor!r}"
    )
    assert fetched.execution_actor == "anthropic/claude-sonnet", (
        "Expected fetched execution_actor 'anthropic/claude-sonnet', "
        f"got {fetched.execution_actor!r}"
    )
    assert fetched.review_actor == "openai/gpt-4", (
        f"Expected fetched review_actor 'openai/gpt-4', got {fetched.review_actor!r}"
    )

    print("wf14-action-actor-refs-ok")


def shared_actions() -> None:
    """List actions from a specific namespace."""
    settings = Settings()
    lifecycle = PlanLifecycleService(settings=settings)

    # Create actions in different namespaces
    lifecycle.create_action(
        name="team-alpha/shared-lint",
        description="Lint code with team standards",
        definition_of_done="All lint checks pass",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
    )
    lifecycle.create_action(
        name="team-beta/shared-test",
        description="Run integration test suite",
        definition_of_done="All tests pass",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
    )
    lifecycle.create_action(
        name="team-alpha/shared-review",
        description="Code review with team conventions",
        definition_of_done="Review complete with no blockers",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
    )

    # Verify total unfiltered count
    all_actions = lifecycle.list_actions()
    assert len(all_actions) == 3, f"Expected 3 total actions, got {len(all_actions)}"
    assert all(a.state == ActionState.AVAILABLE for a in all_actions), (
        "Expected all actions to be AVAILABLE"
    )

    # List only team-alpha namespace
    alpha_actions = lifecycle.list_actions(namespace="team-alpha")
    assert len(alpha_actions) == 2, (
        f"Expected 2 team-alpha actions, got {len(alpha_actions)}"
    )
    alpha_names = [str(a.namespaced_name) for a in alpha_actions]
    assert "team-alpha/shared-lint" in alpha_names, (
        f"'team-alpha/shared-lint' not in {alpha_names}"
    )
    assert "team-alpha/shared-review" in alpha_names, (
        f"'team-alpha/shared-review' not in {alpha_names}"
    )
    # Ensure only team-alpha namespace values are returned.
    assert all(n.startswith("team-alpha/") for n in alpha_names), (
        f"Expected only team-alpha names, got {alpha_names}"
    )

    # List team-beta namespace
    beta_actions = lifecycle.list_actions(namespace="team-beta")
    beta_names = [str(a.namespaced_name) for a in beta_actions]
    assert "team-beta/shared-test" in beta_names, (
        f"'team-beta/shared-test' not in {beta_names}"
    )
    assert len(beta_actions) == 1, f"Expected 1 beta action, got {len(beta_actions)}"

    print("wf14-shared-actions-ok")


def use_shared_action() -> None:
    """Use a shared action to create a plan and verify plan attributes.

    Covers Specification Example 14, Step 3: a developer uses a shared
    action published by another team member.  Verifies that the resulting
    plan inherits the action's namespace and enters the expected initial
    phase and processing state.
    """
    settings = Settings()
    lifecycle = PlanLifecycleService(settings=settings)

    # Publish an action (simulating another team member)
    target_module_arg = ActionArgument.parse(
        "target_module:string:required:Module path to generate tests"
    )
    published = lifecycle.create_action(
        name="team-alpha/generate-tests",
        description="Generate tests for a target module",
        definition_of_done="All target modules have test coverage above 80%",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
        arguments=[target_module_arg],
        tags=["wf14", "shared"],
    )
    assert published.automation_profile == "supervised", (
        "Expected published action automation_profile='supervised', "
        f"got {published.automation_profile!r}"
    )

    # Use the shared action (simulating a second developer)
    plan = lifecycle.use_action(
        action_name="team-alpha/generate-tests",
        project_links=[ProjectLink(project_name="local/payment-service")],
        arguments={"target_module": "src/payments"},
    )

    # Verify plan attributes
    assert plan.namespaced_name.namespace == "team-alpha", (
        f"Expected namespace 'team-alpha', got '{plan.namespaced_name.namespace}'"
    )
    assert plan.phase == PlanPhase.STRATEGIZE, (
        f"Expected phase STRATEGIZE, got {plan.phase}"
    )
    assert plan.processing_state == ProcessingState.QUEUED, (
        f"Expected state QUEUED, got {plan.processing_state}"
    )
    assert plan.action_name == "team-alpha/generate-tests", (
        f"Expected action_name 'team-alpha/generate-tests', got '{plan.action_name}'"
    )
    assert plan.strategy_actor == "openai/gpt-4", (
        f"Expected strategy_actor 'openai/gpt-4', got {plan.strategy_actor!r}"
    )
    assert plan.execution_actor == "openai/gpt-4", (
        f"Expected execution_actor 'openai/gpt-4', got {plan.execution_actor!r}"
    )
    assert plan.arguments == {"target_module": "src/payments"}, (
        "Expected plan arguments {'target_module': 'src/payments'}, "
        f"got {plan.arguments!r}"
    )
    assert plan.arguments_order == ["target_module"], (
        f"Expected arguments_order ['target_module'], got {plan.arguments_order!r}"
    )
    assert len(plan.project_links) == 1, (
        f"Expected 1 project link, got {len(plan.project_links)}"
    )
    assert plan.project_links[0].project_name == "local/payment-service", (
        "Expected project link to local/payment-service, "
        f"got {plan.project_links[0].project_name!r}"
    )
    assert plan.definition_of_done == published.definition_of_done, (
        "Expected plan definition_of_done to match published action"
    )
    assert plan.reusable is True, "Expected plan.reusable=True from action default"
    assert plan.read_only is False, "Expected plan.read_only=False from action default"
    assert plan.tags == ["wf14", "shared"], (
        f"Expected plan tags ['wf14', 'shared'], got {plan.tags!r}"
    )

    print("wf14-use-shared-action-ok")


def plan_namespace() -> None:
    """Create plans and verify namespace-filtered listing.

    Covers Specification Example 14, Step 4: monitoring plans across the
    team by listing plans filtered by namespace.  Creates actions in two
    namespaces, uses them to instantiate plans, and verifies that namespace
    filtering returns only the expected plans.
    """
    settings = Settings()
    lifecycle = PlanLifecycleService(settings=settings)

    # Create and use actions in team-alpha namespace
    lifecycle.create_action(
        name="team-alpha/monitor-lint",
        description="Lint code with team standards",
        definition_of_done="All lint checks pass",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
    )
    lifecycle.create_action(
        name="team-beta/monitor-test",
        description="Run integration test suite",
        definition_of_done="All tests pass",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
        automation_profile="supervised",
    )

    alpha_plan = lifecycle.use_action(action_name="team-alpha/monitor-lint")
    beta_plan = lifecycle.use_action(action_name="team-beta/monitor-test")

    # Move one plan to Execute to validate phase-aware monitoring.
    lifecycle.start_strategize(beta_plan.identity.plan_id)
    lifecycle.complete_strategize(beta_plan.identity.plan_id)
    beta_plan = lifecycle.execute_plan(beta_plan.identity.plan_id)

    # List all plans
    all_plans = lifecycle.list_plans()
    assert len(all_plans) == 2, f"Expected 2 plans total, got {len(all_plans)}"

    # List plans filtered to team-alpha namespace
    alpha_plans = lifecycle.list_plans(namespace="team-alpha")
    assert len(alpha_plans) == 1, f"Expected 1 team-alpha plan, got {len(alpha_plans)}"
    assert alpha_plans[0].identity.plan_id == alpha_plan.identity.plan_id, (
        "team-alpha plan ID mismatch"
    )
    assert alpha_plans[0].phase == PlanPhase.STRATEGIZE, (
        f"Expected team-alpha phase STRATEGIZE, got {alpha_plans[0].phase}"
    )
    assert alpha_plans[0].processing_state == ProcessingState.QUEUED, (
        "Expected team-alpha processing state QUEUED, "
        f"got {alpha_plans[0].processing_state}"
    )
    assert alpha_plans[0].action_name == "team-alpha/monitor-lint", (
        "Expected team-alpha action_name 'team-alpha/monitor-lint', "
        f"got {alpha_plans[0].action_name!r}"
    )

    # List plans filtered to team-beta namespace
    beta_plans = lifecycle.list_plans(namespace="team-beta")
    assert len(beta_plans) == 1, f"Expected 1 team-beta plan, got {len(beta_plans)}"
    assert beta_plans[0].identity.plan_id == beta_plan.identity.plan_id, (
        "team-beta plan ID mismatch"
    )
    assert beta_plans[0].phase == PlanPhase.EXECUTE, (
        f"Expected team-beta phase EXECUTE, got {beta_plans[0].phase}"
    )
    assert beta_plans[0].processing_state == ProcessingState.QUEUED, (
        "Expected team-beta processing state QUEUED after execute transition, "
        f"got {beta_plans[0].processing_state}"
    )
    assert beta_plans[0].action_name == "team-beta/monitor-test", (
        "Expected team-beta action_name 'team-beta/monitor-test', "
        f"got {beta_plans[0].action_name!r}"
    )

    # Verify phase filtering within namespaces.
    alpha_strategize = lifecycle.list_plans(
        namespace="team-alpha", phase=PlanPhase.STRATEGIZE
    )
    assert len(alpha_strategize) == 1, (
        f"Expected 1 team-alpha strategize plan, got {len(alpha_strategize)}"
    )
    assert alpha_strategize[0].identity.plan_id == alpha_plan.identity.plan_id, (
        "team-alpha strategize phase filter returned wrong plan"
    )

    beta_execute = lifecycle.list_plans(namespace="team-beta", phase=PlanPhase.EXECUTE)
    assert len(beta_execute) == 1, (
        f"Expected 1 team-beta execute plan, got {len(beta_execute)}"
    )
    assert beta_execute[0].identity.plan_id == beta_plan.identity.plan_id, (
        "team-beta execute phase filter returned wrong plan"
    )

    # Verify cross-namespace isolation
    gamma_plans = lifecycle.list_plans(namespace="team-gamma")
    assert len(gamma_plans) == 0, f"Expected 0 team-gamma plans, got {len(gamma_plans)}"

    print("wf14-plan-namespace-ok")


# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------

_COMMANDS: dict[str, Callable[[], None]] = {
    "config-server-mode": config_server_mode,
    "diagnostics": diagnostics,
    "action-namespace": action_namespace,
    "action-actor-refs": action_actor_refs,
    "shared-actions": shared_actions,
    "use-shared-action": use_shared_action,
    "plan-namespace": plan_namespace,
}

if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
        print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
        sys.exit(2)
    fn = _COMMANDS[sys.argv[1]]
    fn()
