"""Step definitions for the ``agents project context set`` CLI command.

Extracted from ``project_context_cli_steps.py`` to keep that file under
the 500-line limit mandated by CONTRIBUTING.md. All shared fixtures and
runners are imported back into this module.
"""

from __future__ import annotations

import json
from typing import Any

# third-party
import yaml
from behave import given, then, when  # type: ignore[import-untyped]

# Shared helpers from the parent CLI steps module
from features.steps.project_context_cli_steps import (
    _run_with_container,
)

# ------------------------------------------------------------------
# When steps — context set operations
# ------------------------------------------------------------------


@when(
    'I run context set on "{project}" with view "{view}" and include-resource "{res}"'
)
def step_ctx_set_include_res(context: Any, project: str, view: str, res: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        include_resource=[res],
    )


@when(
    'I run context set on "{project}" with view "{view}" and exclude-resource "{res}"'
)
def step_ctx_set_exclude_res(context: Any, project: str, view: str, res: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        exclude_resource=[res],
    )


@when('I run context set on "{project}" with view "{view}" and include-path "{path}"')
def step_ctx_set_include_path(context: Any, project: str, view: str, path: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        include_path=[path],
    )


@when('I run context set on "{project}" with view "{view}" and exclude-path "{path}"')
def step_ctx_set_exclude_path(context: Any, project: str, view: str, path: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        exclude_path=[path],
    )


@when('I run context set spec example on "{project}" with format "{fmt}"')
def step_ctx_set_spec_example(context: Any, project: str, fmt: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view="strategize",
        include_resource=["repo"],
        exclude_path=["**/node_modules/**"],
        hot_max_tokens=12_000,
        warm_max_decisions=50,
        cold_max_decisions=200,
        query_limit=20,
        max_file_size=1_048_576,
        max_total_size=50 * 1_048_576,
        summarize=True,
        summary_max_tokens=800,
        output_format=fmt,
    )


@when('I run context set on "{project}" with view "{view}" and max-file-size {size:d}')
def step_ctx_set_max_file_size(
    context: Any, project: str, view: str, size: int
) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        max_file_size=size,
    )


@when('I run context set on "{project}" with view "{view}" and max-total-size {size:d}')
def step_ctx_set_max_total_size(
    context: Any, project: str, view: str, size: int
) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        max_total_size=size,
    )


@when('I run context set on "{project}" with invalid view "{view}"')
def step_ctx_set_invalid_view(context: Any, project: str, view: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
    )


@when('I run context set on "{project}" with view "{view}" and clear flag')
def step_ctx_set_clear(context: Any, project: str, view: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view=view,
        clear=True,
    )


# ------------------------------------------------------------------
# Given steps — context set pre-conditions
# ------------------------------------------------------------------


@given('I have set a strategize view on "{project}" with defaults')
def step_given_strategize_view(context: Any, project: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view="strategize",
        include_resource=["strat-default"],
    )


@given('I have set a default view on "{project}" with include-resource "{res}"')
def step_given_default_view(context: Any, project: str, res: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view="default",
        include_resource=[res],
    )


@given('I have set a strategize view on "{project}" with include-resource "{res}"')
def step_given_strategize_view_res(context: Any, project: str, res: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view="strategize",
        include_resource=[res],
    )


@given('I have set an execute view on "{project}" with include-resource "{res}"')
def step_given_execute_view_res(context: Any, project: str, res: str) -> None:
    from cleveragents.cli.commands.project_context import (
        context_set,
    )

    _run_with_container(
        context,
        context_set,
        project=project,
        view="execute",
        include_resource=[res],
    )


# ------------------------------------------------------------------
# Then assertions — context set output and policy verification
# ------------------------------------------------------------------


@then("the context set command should succeed")
def step_ctx_set_ok(context: Any) -> None:
    assert context.ctx_exit_code == 0, (
        f"Expected exit 0, got {context.ctx_exit_code}. Output: {context.ctx_output}"
    )


@then("the context set command should fail")
def step_ctx_set_fail(context: Any) -> None:
    assert context.ctx_exit_code != 0, (
        f"Expected non-zero exit, got {context.ctx_exit_code}"
    )


@then("the context set JSON output should match the spec envelope")
def step_ctx_set_json_spec(context: Any) -> None:
    output = context.ctx_output.strip()
    assert output, "No CLI output captured"
    payload = json.loads(output)

    assert payload.get("command") == "project context set"
    assert payload.get("status") == "ok"
    assert payload.get("exit_code") == 0

    timing = payload.get("timing", {})
    assert isinstance(timing.get("duration_ms"), int)

    messages = payload.get("messages", [])
    assert messages and messages[0].get("text") == "Context policy updated"

    data = payload.get("data", {})

    context_policy = data.get("context_policy", {})
    assert context_policy.get("project") == "local/ctx-app"
    assert context_policy.get("view") == "strategize"
    assert context_policy.get("include_resources") == ["repo"]
    assert context_policy.get("exclude_paths") == ["**/node_modules/**"]

    limits = data.get("limits", {})
    assert limits.get("hot_max_tokens") == 12_000
    assert limits.get("warm_max_decisions") == 50
    assert limits.get("cold_max_decisions") == 200
    assert limits.get("query_limit") == 20
    assert limits.get("max_file_size") == "1 MB"
    assert limits.get("max_total_size") == "50 MB"

    summarization = data.get("summarization", {})
    assert summarization.get("enabled") is True
    assert summarization.get("max_tokens") == 800

    other_views = data.get("other_views", {})
    assert other_views.get("default") == "(unset)"
    assert other_views.get("execute") == "(default)"
    assert other_views.get("apply") == "(default)"


@then("the context set YAML output should match the spec envelope")
def step_ctx_set_yaml_spec(context: Any) -> None:
    output = context.ctx_output.strip()
    assert output, "No CLI output captured"
    payload = yaml.safe_load(output)

    assert payload.get("command") == "project context set"
    assert payload.get("status") == "ok"
    assert payload.get("exit_code") == 0

    timing = payload.get("timing", {})
    assert isinstance(timing.get("duration_ms"), int)

    messages = payload.get("messages", [])
    assert messages and messages[0].get("text") == "Context policy updated"

    data = payload.get("data", {})
    context_policy = data.get("context_policy", {})
    assert context_policy.get("project") == "local/ctx-app"
    assert context_policy.get("view") == "strategize"
    assert context_policy.get("include_resources") == ["repo"]
    assert context_policy.get("exclude_paths") == ["**/node_modules/**"]

    limits = data.get("limits", {})
    assert limits.get("hot_max_tokens") == 12_000
    assert limits.get("warm_max_decisions") == 50
    assert limits.get("cold_max_decisions") == 200
    assert limits.get("query_limit") == 20
    assert limits.get("max_file_size") == "1 MB"
    assert limits.get("max_total_size") == "50 MB"

    summarization = data.get("summarization", {})
    assert summarization.get("enabled") is True
    assert summarization.get("max_tokens") == 800

    other_views = data.get("other_views", {})
    assert other_views.get("default") == "(unset)"
    assert other_views.get("execute") == "(default)"
    assert other_views.get("apply") == "(default)"


@then("the context set rich output should include spec panels")
def step_ctx_set_rich_spec(context: Any) -> None:
    output = context.ctx_output
    assert "Context Policy" in output
    assert "Limits" in output
    assert "Summarization" in output
    assert "Other Views" in output
    assert "Project: local/ctx-app" in output
    assert "View: strategize" in output
    assert "Include: repo" in output
    assert "Exclude: **/node_modules/**" in output
    assert "Hot Tokens: 12000 (soft cap)" in output
    assert "Warm Decisions: 50" in output
    assert "Cold Decisions: 200" in output
    assert "Query Limit: 20" in output
    assert "Max File Size: 1 MB" in output
    assert "Max Total Size: 50 MB" in output
    assert "Enabled: yes" in output
    assert "Max Tokens: 800" in output
    assert "default: (unset)" in output
    assert "execute: (default)" in output
    assert "apply: (default)" in output
    assert "\u2713 Context policy updated" in output


def _read_stored_policy(context: Any) -> Any:
    """Read the stored policy from the test DB."""
    from cleveragents.cli.commands.project_context import (
        _read_policy,
    )

    return _read_policy(context.ctx_session_factory, "local/ctx-app")


@then('the stored policy default view should include resource "{res}"')
def step_policy_include_res(context: Any, res: str) -> None:
    policy = _read_stored_policy(context)
    assert res in policy.default_view.include_resources, (
        f"{res} not in {policy.default_view.include_resources}"
    )


@then('the stored policy default view should exclude resource "{res}"')
def step_policy_exclude_res(context: Any, res: str) -> None:
    policy = _read_stored_policy(context)
    assert res in policy.default_view.exclude_resources, (
        f"{res} not in {policy.default_view.exclude_resources}"
    )


@then('the stored policy default view should include path "{path}"')
def step_policy_include_path(context: Any, path: str) -> None:
    policy = _read_stored_policy(context)
    assert path in policy.default_view.include_paths, (
        f"{path} not in {policy.default_view.include_paths}"
    )


@then('the stored policy default view should exclude path "{path}"')
def step_policy_exclude_path(context: Any, path: str) -> None:
    policy = _read_stored_policy(context)
    assert path in policy.default_view.exclude_paths, (
        f"{path} not in {policy.default_view.exclude_paths}"
    )


@then("the stored policy default view max file size should be {size:d}")
def step_policy_max_file_size(context: Any, size: int) -> None:
    policy = _read_stored_policy(context)
    assert policy.default_view.max_file_size == size, (
        f"Expected {size}, got {policy.default_view.max_file_size}"
    )


@then("the stored policy default view max total size should be {size:d}")
def step_policy_max_total_size(context: Any, size: int) -> None:
    policy = _read_stored_policy(context)
    assert policy.default_view.max_total_size == size, (
        f"Expected {size}, got {policy.default_view.max_total_size}"
    )


@then('the stored policy strategize view should include resource "{res}"')
def step_policy_strat_include_res(context: Any, res: str) -> None:
    policy = _read_stored_policy(context)
    assert policy.strategize_view is not None, "strategize_view is None"
    assert res in policy.strategize_view.include_resources


@then("the stored policy strategize view should be None")
def step_policy_strat_none(context: Any) -> None:
    policy = _read_stored_policy(context)
    assert policy.strategize_view is None, (
        f"Expected None, got {policy.strategize_view}"
    )
