"""Step definitions for global --format flag propagation tests.

Tests that the --format flag is a global option on the root `agents` command
(via the Typer callback), not a per-command option, per the specification.
"""

from __future__ import annotations

import json
from typing import Any
from unittest.mock import patch

import yaml
from behave import given, then, when
from typer.testing import CliRunner

from cleveragents.cli.main import app


@given("the global format flag test runner is set up")
def step_setup_runner(context: Any) -> None:
    """Set up the test runner for global format flag tests."""
    context.runner = CliRunner()
    context.cli_result = None
    context.ctx_obj = {}


def _invoke_with_mocked_system(runner: CliRunner, args: list[str]) -> Any:
    """Invoke the CLI app with mocked system commands."""
    with (
        patch(
            "cleveragents.cli.commands.system.build_version_data",
            return_value={
                "version": "3.7.0",
                "python": "3.12.0",
                "platform": "linux",
            },
        ),
        patch(
            "cleveragents.cli.commands.system.render_version_rich",
        ),
        patch(
            "cleveragents.cli.commands.system.build_info_data",
            return_value={
                "version": "3.7.0",
                "config_path": "/tmp/config.toml",
                "db_url": "sqlite:///test.db",
            },
        ),
        patch(
            "cleveragents.cli.commands.system.render_info_rich",
        ),
        patch(
            "cleveragents.cli.commands.system.build_diagnostics_data",
            return_value={
                "checks": [],
                "has_errors": False,
            },
        ),
        patch(
            "cleveragents.cli.commands.system.render_diagnostics_rich",
        ),
    ):
        return runner.invoke(app, args)


@when('I invoke the CLI with global args "{args_str}"')
def step_invoke_cli_global_args(context: Any, args_str: str) -> None:
    """Invoke the CLI with the given argument string."""
    args = args_str.split()
    result = _invoke_with_mocked_system(context.runner, args)
    context.cli_result = result
    context.cli_output = result.output


@then("the global format command should succeed")
def step_global_format_command_succeeds(context: Any) -> None:
    """Verify the command exited with code 0."""
    result = context.cli_result
    assert result.exit_code == 0, (
        f"Expected exit code 0, got {result.exit_code}.\n"
        f"Output: {result.output}\n"
        f"Exception: {result.exception}"
    )


@then("the global format output should be valid JSON")
def step_global_format_output_is_valid_json(context: Any) -> None:
    """Verify the output is parseable JSON."""
    output = context.cli_output.strip()
    try:
        parsed = json.loads(output)
        context.parsed_json = parsed
    except json.JSONDecodeError as exc:
        raise AssertionError(
            f"Output is not valid JSON: {exc}\nOutput was: {output!r}"
        ) from exc


@then("the global format output should be valid YAML")
def step_global_format_output_is_valid_yaml(context: Any) -> None:
    """Verify the output is parseable YAML."""
    output = context.cli_output.strip()
    try:
        parsed = yaml.safe_load(output)
        context.parsed_yaml = parsed
    except yaml.YAMLError as exc:
        raise AssertionError(
            f"Output is not valid YAML: {exc}\nOutput was: {output!r}"
        ) from exc


@then("the global format output should contain plain text")
def step_global_format_output_contains_plain_text(context: Any) -> None:
    """Verify the output contains plain key: value text."""
    output = context.cli_output.strip()
    assert output, f"Expected non-empty plain text output, got: {output!r}"


@then('the global format JSON output should contain key "{key}"')
def step_global_format_json_output_contains_key(context: Any, key: str) -> None:
    """Verify the parsed JSON output contains the given key."""
    parsed = getattr(context, "parsed_json", None)
    if parsed is None:
        output = context.cli_output.strip()
        parsed = json.loads(output)
    assert key in parsed, (
        f"Expected key '{key}' in JSON output. Keys found: {list(parsed.keys())}"
    )


@when('I call main_callback directly with format "{fmt}"')
def step_call_main_callback_directly(context: Any, fmt: str) -> None:
    """Call main_callback via the CLI runner to test ctx.obj storage."""
    result = _invoke_with_mocked_system(context.runner, ["--format", fmt, "version"])
    context.cli_result = result
    context.captured_fmt = fmt


@then('the context object should have format "{expected_fmt}"')
def step_ctx_obj_has_format(context: Any, expected_fmt: str) -> None:
    """Verify the format was stored correctly.

    Since we can't easily inspect ctx.obj after the fact without modifying
    the command, we verify indirectly: the command succeeded and the format
    was accepted (no 'Invalid value' error for the format option).
    """
    result = context.cli_result
    # The command should succeed (format was accepted)
    assert result.exit_code == 0, (
        f"Expected exit code 0 for format '{expected_fmt}', "
        f"got {result.exit_code}.\nOutput: {result.output}"
    )
    # Verify no "Invalid value" error for the format option
    assert "Invalid value" not in result.output, (
        f"Got 'Invalid value' error for format '{expected_fmt}': {result.output}"
    )
    context.verified_fmt = expected_fmt
