"""Step definitions for TDD Bug #1023 — implicit init requirement.

These steps exercise real CLI commands in a fresh environment where
``agents init`` has NOT been explicitly run.  The environment variable
``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is set, which *should*
cause the database to be created implicitly.

Bug #1023 reports that ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true``
triggers schema migrations on an existing database but does NOT create
the database file or its parent directory structure.  As a result, any
CLI command that touches the database (e.g., ``resource add``,
``project create``) fails with:

    sqlite3.OperationalError: unable to open database file

The assertions here expect the commands to succeed (exit code 0),
proving implicit init works and preventing regressions.

Mock strategy
~~~~~~~~~~~~~
No mocks are needed — we exercise the real CLI via Typer's CliRunner
in a pristine temporary directory.  We only manipulate environment
variables to create a fresh, uninitialised CLEVERAGENTS_HOME.  We
remove ``CLEVERAGENTS_DATABASE_URL``, ``CLEVERAGENTS_TEST_DATABASE_URL``,
``BEHAVE_TESTING``, and ``CLEVERAGENTS_TEMPLATE_DB`` so the container
derives the DB path from ``CLEVERAGENTS_HOME``, migration prompts are
not auto-approved by the testing guard, and the template-DB fast-path
does not silently create the database for us.
"""

from __future__ import annotations

import os
import shutil
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

runner = CliRunner()


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given("a fresh isolated environment for tdd-implicit-init")
def step_fresh_environment(context: Context) -> None:
    """Set up a fresh temporary environment with no existing database.

    Creates a temporary directory to serve as ``CLEVERAGENTS_HOME``.
    Does NOT run ``agents init`` — the entire point of this TDD test is
    to verify that CLI commands work without it.

    Removes ``BEHAVE_TESTING`` so the migration runner's auto-approve
    guard does not mask the real code path.
    """
    tmpdir = tempfile.mkdtemp(prefix="tdd_implicit_init_")
    context.tdd_implicit_init_tmpdir = tmpdir

    # Save and remove env vars that point to pre-migrated databases,
    # template-DB fast-paths, or testing guards that auto-approve
    # migration prompts.  CLEVERAGENTS_TEMPLATE_DB is particularly
    # important: when set, the Behave template-DB patch copies a
    # pre-migrated database, silently bypassing the directory-creation
    # bug this test is designed to capture.
    saved_env: dict[str, str | None] = {
        k: os.environ.pop(k, None)
        for k in (
            "CLEVERAGENTS_AUTO_APPLY_MIGRATIONS",
            "CLEVERAGENTS_DATABASE_URL",
            "CLEVERAGENTS_TEST_DATABASE_URL",
            "CLEVERAGENTS_HOME",
            "BEHAVE_TESTING",
            "CLEVERAGENTS_TEMPLATE_DB",
            "CLEVERAGENTS_TESTING_USE_MOCK_AI",
        )
    }
    context.tdd_implicit_init_saved_env = saved_env

    # Point CLEVERAGENTS_HOME at our pristine temp directory.
    os.environ["CLEVERAGENTS_HOME"] = tmpdir

    # Force an isolated SQLite database path under this scenario's temp
    # directory. This avoids leaking state from repository-level local
    # database files created by prior test runs.
    db_url = f"sqlite:///{tmpdir}/db/cleveragents.db"
    os.environ["CLEVERAGENTS_DATABASE_URL"] = db_url
    os.environ["CLEVERAGENTS_TEST_DATABASE_URL"] = db_url

    # Reset the DI container so it picks up our env overrides.
    reset_container()

    def _cleanup() -> None:
        for key, val in saved_env.items():
            if val is not None:
                os.environ[key] = val
            else:
                os.environ.pop(key, None)
        reset_container()
        shutil.rmtree(tmpdir, ignore_errors=True)

    context.add_cleanup(_cleanup)


@given("CLEVERAGENTS_AUTO_APPLY_MIGRATIONS is set to true")
def step_set_auto_apply_migrations(context: Context) -> None:
    """Ensure CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true is in the environment.

    This is the env var that should, in theory, trigger implicit
    database creation and migration.  Bug #1023 reports it does not
    create the directory structure.
    """
    os.environ["CLEVERAGENTS_AUTO_APPLY_MIGRATIONS"] = "true"


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when('I run the CLI command "{command}" without prior init')
def step_run_cli_command_without_init(context: Context, command: str) -> None:
    """Invoke a CLI command via CliRunner without running ``agents init`` first.

    The command string is split into tokens and passed to the main CLI
    app.  ``CLEVERAGENTS_HOME`` is set to the pristine temp directory
    and ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is in the
    environment.

    Exceptions are caught by CliRunner (the default behaviour) so that
    the result object always carries an ``exit_code`` and ``output``.
    This is essential for ``@tdd_expected_fail`` inversion — if
    exceptions propagated as non-AssertionError, the inversion guard
    in ``apply_tdd_inversion`` would skip inversion and the scenario
    would be reported as a hard failure instead of an expected failure.
    """
    # Reset container again to ensure a clean UoW derivation.
    reset_container()

    args = command.split()
    context.tdd_implicit_init_result = runner.invoke(app, args)


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then("the tdd-implicit-init command should exit with code {code:d}")
def step_exit_code(context: Context, code: int) -> None:
    """Assert the CLI command exited with the expected return code."""
    result = context.tdd_implicit_init_result
    assert result is not None, "CLI command was not invoked"
    actual = result.exit_code
    assert actual == code, (
        f"Expected exit code {code}, got {actual}.\nOutput:\n{result.output}"
    )


@then('the tdd-implicit-init command output should contain "{text}"')
def step_output_contains(context: Context, text: str) -> None:
    """Assert the CLI command output contains the expected text."""
    result = context.tdd_implicit_init_result
    assert result is not None, "CLI command was not invoked"
    assert text in result.output, (
        f"Expected output to contain {text!r}.\nActual output:\n{result.output}"
    )
