"""Step definitions for TDD Issue #980 — skill add cross-process persistence.

These steps capture bug #980 by exercising the *real* CLI code path
across independent process-like invocations.  The existing persistence
tests (``skill_add_persist.feature``) verify round-trip within the
same Python process — they create two ``SkillService`` instances
sharing the same in-memory SQLAlchemy session factory.  That approach
cannot detect the cross-process regression introduced by PR #640.

This test:

1. Points the CLI at the per-scenario SQLite database (already set up
   by ``before_scenario`` with all migrations applied).
2. Runs ``agents skill add --config <file> --format json`` via the
   CLI app with a fresh DI container (simulating process 1).
3. Resets the DI container and runs ``agents skill list --format json``
   (simulating process 2) — the SkillService is rebuilt from scratch.
4. Asserts the skill appears in the listing.

The ``@tdd_expected_fail`` tag on the feature inverts the assertion
failure to a CI pass while the bug is unfixed.  The root cause is that
``_build_skill_service`` creates a standalone engine that connects to
the database but the ``skills`` table does not exist in databases
initialised by ``agents init`` (the Alembic migration for skills was
never added, or the skill service bypasses migration-managed tables).
"""

from __future__ import annotations

import contextlib
import os
import tempfile

from behave import given, then, when
from behave.runner import Context
from typer.testing import CliRunner

from cleveragents.application.container import reset_container
from cleveragents.cli.main import app as root_app

runner = CliRunner()

_SKILL_YAML = """\
name: local/tdd-cross-process
description: "TDD cross-process persistence test skill"

tools:
  - name: builtin/read_file
"""

_SKILL_NAME = "local/tdd-cross-process"


# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------


@given("a cross-process skill persistence environment")
def step_cross_process_env(context: Context) -> None:
    """Write a skill YAML config file for use in subsequent steps.

    The per-scenario database is already set up by ``before_scenario``
    in ``environment.py`` (with migrations applied via the template-DB
    fast path).  We only need to create the skill YAML and ensure the
    DI container starts from a clean state.
    """
    # Write skill YAML config to a temp file
    fd, config_path = tempfile.mkstemp(suffix=".yaml", prefix="tdd_skill_")
    with os.fdopen(fd, "w") as fh:
        fh.write(_SKILL_YAML)
    context.tdd_skill_config_path = config_path

    # Register cleanup for the temp config file
    def _cleanup() -> None:
        with contextlib.suppress(OSError):
            os.unlink(config_path)
        reset_container()

    context.add_cleanup(_cleanup)

    # Start with a clean container
    reset_container()


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when("I add a skill via a CLI subprocess")
def step_add_skill_subprocess(context: Context) -> None:
    """Run ``agents skill add --config <file> --format json``.

    Simulates CLI process 1.  After this call, the DI container and its
    in-memory SkillService hold the newly added skill.  The skill
    *should* also be persisted to the SQLite database so that process 2
    can see it.
    """
    result = runner.invoke(
        root_app,
        [
            "skill",
            "add",
            "--config",
            context.tdd_skill_config_path,
            "--format",
            "json",
        ],
    )
    context.tdd_skill_add_output = result.output
    context.tdd_skill_add_exit_code = result.exit_code


@when("I list skills via a separate CLI subprocess")
def step_list_skills_subprocess(context: Context) -> None:
    """Run ``agents skill list --format json`` with a fresh DI container.

    The DI container is reset before the call to simulate a completely
    independent CLI process — this is the cross-process boundary that
    exposes bug #980.  The new SkillService instance must load skills
    from the database to find the skill added in the previous step.
    """
    # Simulate cross-process boundary: fresh container = fresh SkillService
    reset_container()

    result = runner.invoke(
        root_app,
        ["skill", "list", "--format", "json"],
    )
    context.tdd_skill_list_output = result.output
    context.tdd_skill_list_exit_code = result.exit_code


@when("I show the skill via a separate CLI subprocess")
def step_show_skill_subprocess(context: Context) -> None:
    """Run ``agents skill show <name> --format json`` with a fresh container.

    The DI container is reset before the call to simulate a completely
    independent CLI process.
    """
    # Simulate cross-process boundary
    reset_container()

    result = runner.invoke(
        root_app,
        ["skill", "show", _SKILL_NAME, "--format", "json"],
    )
    context.tdd_skill_show_output = result.output
    context.tdd_skill_show_exit_code = result.exit_code


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then("the cross-process skill list should contain the added skill")
def step_assert_skill_in_list(context: Context) -> None:
    """Assert that the skill added in one process is visible in another.

    Checks the output of ``skill list`` for the skill name.  This
    assertion will FAIL while bug #980 is present because the second
    process creates a fresh SkillService that does not find the skill
    in the database (the ``skills`` table is missing or was never
    populated due to the DB-fallback in ``_build_skill_service``).

    The ``@tdd_expected_fail`` tag inverts this failure to a CI pass.
    """
    list_output = context.tdd_skill_list_output

    assert _SKILL_NAME in list_output, (
        f"Expected skill '{_SKILL_NAME}' in cross-process skill list output "
        f"but it was not found.\n"
        f"Add exit code: {context.tdd_skill_add_exit_code}\n"
        f"Add output:\n{context.tdd_skill_add_output}\n"
        f"List exit code: {context.tdd_skill_list_exit_code}\n"
        f"List output:\n{list_output}"
    )


@then("the cross-process skill show output should contain the skill name")
def step_assert_skill_show_contains_name(context: Context) -> None:
    """Assert that ``skill show`` in a separate process finds the skill.

    This assertion will FAIL while bug #980 is present because the
    second process cannot find the skill in its fresh service instance.
    """
    show_output = context.tdd_skill_show_output
    show_exit = context.tdd_skill_show_exit_code

    assert show_exit == 0 and _SKILL_NAME in show_output, (
        f"Expected skill '{_SKILL_NAME}' visible via cross-process "
        f"'skill show' but it was not found.\n"
        f"Show exit code: {show_exit}\n"
        f"Show output:\n{show_output}\n"
        f"Add exit code: {context.tdd_skill_add_exit_code}\n"
        f"Add output:\n{context.tdd_skill_add_output}"
    )
