"""Step definitions for TDD Issue #1024 — SQLite DB URL CWD resolution.

These steps verify that when ``CLEVERAGENTS_HOME`` is set to a temporary
directory, the SQLite database file is created **inside** that directory
rather than relative to the current working directory.

The default ``database_url`` in ``Settings`` is ``sqlite:///cleveragents.db``
— a relative path that resolves against CWD.  This breaks test isolation
when ``CLEVERAGENTS_HOME`` points to a different directory.

Bug: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/1024
TDD: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/1034
"""

from __future__ import annotations

import logging
import os
import shutil
import tempfile
from pathlib import Path

from behave import given, then, when
from behave.runner import Context

from cleveragents.application.container import get_database_url, reset_container
from cleveragents.config.settings import Settings


def _suppress_structlog_stdout() -> tuple[int, bool]:
    """Prevent structlog debug lines from contaminating CLI stdout."""
    import structlog

    root = logging.getLogger()
    prev_level = root.level
    root.setLevel(logging.WARNING)

    prev_config = structlog.get_config()
    prev_cache: bool = prev_config.get("cache_logger_on_first_use", True)  # type: ignore[assignment]
    structlog.configure(
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=False,
    )

    return prev_level, prev_cache


def _restore_structlog(prev_level: int, prev_cache: bool) -> None:
    """Undo the suppression applied by :func:`_suppress_structlog_stdout`."""
    import structlog

    logging.getLogger().setLevel(prev_level)
    structlog.configure(cache_logger_on_first_use=prev_cache)


def _extract_sqlite_path(url: str) -> Path | None:
    """Extract the file path from a SQLite URL.

    Handles both ``sqlite:///path`` (relative) and
    ``sqlite:////abs/path`` (absolute) forms.
    """
    prefix = "sqlite:///"
    if not url.startswith(prefix):
        return None
    raw_path = url[len(prefix) :]
    if not raw_path:
        return None
    return Path(raw_path).resolve()


@given("a fresh CLEVERAGENTS_HOME temp directory for DB URL testing")
def step_fresh_home(context: Context) -> None:
    """Create an isolated CLEVERAGENTS_HOME and record the original CWD."""
    # Reset singletons to avoid stale state
    reset_container()
    Settings._instance = None  # type: ignore[attr-defined]

    prev_level, prev_cache = _suppress_structlog_stdout()

    context.original_cwd = Path.cwd().resolve()
    context.tdd_home = tempfile.mkdtemp(prefix="tdd_sqlite_url_1024_")

    # Remove any env vars that could override database_url
    context.saved_db_url = os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
    context.saved_test_db_url = os.environ.pop("CLEVERAGENTS_TEST_DATABASE_URL", None)

    os.environ["CLEVERAGENTS_HOME"] = context.tdd_home

    # Reset singletons so new Settings picks up the environment
    reset_container()
    Settings._instance = None  # type: ignore[attr-defined]

    def _cleanup() -> None:
        # Restore env vars
        if context.saved_db_url is not None:
            os.environ["CLEVERAGENTS_DATABASE_URL"] = context.saved_db_url
        else:
            os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
        if context.saved_test_db_url is not None:
            os.environ["CLEVERAGENTS_TEST_DATABASE_URL"] = context.saved_test_db_url
        else:
            os.environ.pop("CLEVERAGENTS_TEST_DATABASE_URL", None)

        os.environ.pop("CLEVERAGENTS_HOME", None)
        reset_container()
        Settings._instance = None  # type: ignore[attr-defined]

        shutil.rmtree(context.tdd_home, ignore_errors=True)
        _restore_structlog(prev_level, prev_cache)

    context.add_cleanup(_cleanup)


@when("I resolve the effective database URL from Settings")
def step_resolve_db_url(context: Context) -> None:
    """Resolve the effective database URL via the container helper."""
    # Use the container's get_database_url which is what the app uses
    context.resolved_url = get_database_url()
    context.resolved_path = _extract_sqlite_path(context.resolved_url)


@when("I resolve the Settings database_url default")
def step_resolve_settings_default(context: Context) -> None:
    """Resolve the database_url via the Settings model default.

    Constructs a fresh Settings instance (with CLEVERAGENTS_DATABASE_URL
    removed) to exercise the default ``database_url`` field, then resolves
    the path.  This tests the Settings-level default independently of the
    container's ``get_database_url()`` helper.
    """
    settings = Settings()
    context.settings_db_url = settings.get_database_url()
    context.settings_db_path = _extract_sqlite_path(context.settings_db_url)


@then("the resolved database path should be inside CLEVERAGENTS_HOME")
def step_path_inside_home(context: Context) -> None:
    """Assert the database path is under CLEVERAGENTS_HOME."""
    home = Path(context.tdd_home).resolve()
    db_path = context.resolved_path
    assert db_path is not None, (
        f"Could not extract a file path from database URL: {context.resolved_url}"
    )
    assert str(db_path).startswith(str(home)), (
        f"Database path {db_path} is NOT inside CLEVERAGENTS_HOME {home}.\n"
        f"Full database URL: {context.resolved_url}"
    )


@then("the resolved database path should not be inside the original CWD")
def step_path_not_in_cwd(context: Context) -> None:
    """Assert the database path is NOT under the original CWD."""
    cwd = context.original_cwd
    db_path = context.resolved_path
    assert db_path is not None, (
        f"Could not extract a file path from database URL: {context.resolved_url}"
    )
    # The DB should not resolve to a child of the original CWD
    # (unless CLEVERAGENTS_HOME happens to be inside CWD, which it isn't
    # for our temp directory setup).
    home = Path(context.tdd_home).resolve()
    if not str(home).startswith(str(cwd)):
        # Only check if home is NOT under cwd (normal case)
        assert not str(db_path).startswith(str(cwd)), (
            f"Database path {db_path} is inside CWD {cwd} instead of "
            f"CLEVERAGENTS_HOME {home}.\n"
            f"Full database URL: {context.resolved_url}"
        )


@then("the settings database path should be inside CLEVERAGENTS_HOME")
def step_settings_path_inside_home(context: Context) -> None:
    """Assert the Settings-level database_url resolves inside CLEVERAGENTS_HOME."""
    home = Path(context.tdd_home).resolve()
    db_path = context.settings_db_path
    assert db_path is not None, (
        f"Could not extract a file path from Settings database URL: "
        f"{context.settings_db_url}"
    )
    assert str(db_path).startswith(str(home)), (
        f"Settings database path {db_path} is NOT inside "
        f"CLEVERAGENTS_HOME {home}.\n"
        f"Full Settings database URL: {context.settings_db_url}"
    )
