"""Step definitions for repo_indexing_service_coverage.feature.

Targets uncovered error-handling paths in repo_indexing_service.py:
- Lines 182-185: walk failure + persist_status double-fault (inner except)
- Lines 219-228: persist_index failure on fresh resource -> persist ERROR status
- Lines 231-234, 236: persist_index failure + persist_status double-fault
- Lines 367-368+: refresh_index persist_index failure
"""

from __future__ import annotations

import logging
import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch

from behave import given, then, when
from behave.runner import Context
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import cleveragents.application.services.repo_indexing_service as svc_mod
from cleveragents.application.services.repo_indexing_service import (
    RepoIndexingService,
)
from cleveragents.domain.models.core.repo_index import IndexStatus
from cleveragents.infrastructure.database.models import Base

# Valid ULID for test resource
_RESOURCE_ID = "01KK0D8WNATFNEX2JMG5GKF6FM"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_service(context: Context) -> RepoIndexingService:
    """Create an in-memory RepoIndexingService and store on context."""
    engine = create_engine("sqlite:///:memory:", echo=False)
    Base.metadata.create_all(engine)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    service = RepoIndexingService(session_factory=factory)
    context.cov_service = service
    context.cov_error = None
    context.cov_logged_warnings = []
    return service


def _make_sample_files(context: Context) -> Path:
    """Create a temp directory with a few files."""
    tmpdir = Path(tempfile.mkdtemp())
    (tmpdir / "app.py").write_text("print('hello')\n")
    (tmpdir / "lib.py").write_text("x = 42\n")
    context.cov_tmpdir = str(tmpdir)
    context.add_cleanup(_cleanup, context)
    return tmpdir


def _cleanup(context: Context) -> None:
    tmpdir = getattr(context, "cov_tmpdir", None)
    if tmpdir:
        shutil.rmtree(tmpdir, ignore_errors=True)


class _WarningCapture(logging.Handler):
    """Logging handler that captures WARNING-level messages."""

    def __init__(self, store: list[str]) -> None:
        super().__init__(level=logging.WARNING)
        self._store = store

    def emit(self, record: logging.LogRecord) -> None:
        self._store.append(record.getMessage())


def _install_log_capture(context: Context) -> None:
    """Install a WARNING-level log capture handler on the service logger.

    Also monkey-patches logger.warning to directly capture records, bypassing
    any structlog/logging pipeline issues in the full test suite where
    configure_structlog() may have intercepted the logging machinery.
    """
    svc_logger = logging.getLogger(
        "cleveragents.application.services.repo_indexing_service"
    )
    svc_logger.disabled = False
    handler = _WarningCapture(context.cov_logged_warnings)
    svc_logger.addHandler(handler)

    _original_warning = svc_logger.warning

    def _capturing_warning(msg, *args, **kwargs):
        context.cov_logged_warnings.append(msg % args if args else msg)
        return _original_warning(msg, *args, **kwargs)

    svc_logger.warning = _capturing_warning

    def _cleanup(ctx):
        svc_logger.warning = _original_warning
        svc_logger.removeHandler(handler)

    context.add_cleanup(_cleanup, context)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("a coverage-boost fresh in-memory indexing service")
def step_fresh_service(context: Context) -> None:
    _make_service(context)


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


@given("a coverage-boost temporary directory with sample files")
def step_sample_dir(context: Context) -> None:
    _make_sample_files(context)


@given("a coverage-boost successful full index exists")
def step_existing_index(context: Context) -> None:
    service: RepoIndexingService = context.cov_service
    result = service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
    context.cov_original_result = result


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


@when(
    "walk_and_index is mocked to fail and persist_status also fails on a fresh resource"
)
def step_walk_and_persist_status_double_fault(context: Context) -> None:
    """Exercise lines 182-185: walk fails, then persist_status(ERROR) also fails.

    On a fresh resource (no prior good index), the service first calls
    persist_status(INDEXING) at line 147, then walks. When walk fails,
    it tries persist_status(ERROR) at line 175. If THAT also fails,
    lines 182-185 catch the inner exception and log a warning.

    We allow the first persist_status(INDEXING) call through, but make
    the second persist_status(ERROR) call fail.
    """
    service: RepoIndexingService = context.cov_service
    _install_log_capture(context)

    original_persist_status = svc_mod.persist_status
    call_count = {"n": 0}

    def _persist_status_fail_on_error(*args, **kwargs):
        call_count["n"] += 1
        status_arg = kwargs.get("status")
        if status_arg == IndexStatus.ERROR:
            raise RuntimeError("Simulated persist_status failure on ERROR")
        return original_persist_status(*args, **kwargs)

    with (
        patch.object(
            svc_mod,
            "walk_and_index",
            side_effect=OSError("Simulated walk failure"),
        ),
        patch.object(
            svc_mod,
            "persist_status",
            side_effect=_persist_status_fail_on_error,
        ),
    ):
        try:
            service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
        except Exception as exc:
            context.cov_error = exc


@when("walk_and_index succeeds but persist_index fails on a fresh resource")
def step_persist_index_fails_fresh(context: Context) -> None:
    """Exercise lines 219-228: persist_index fails on fresh resource.

    After a successful walk, persist_index raises. Because there is no
    prior good index (has_good_index=False), the service tries
    persist_status(ERROR) at lines 221-230, then re-raises at line 236.
    """
    service: RepoIndexingService = context.cov_service

    with patch.object(
        svc_mod,
        "persist_index",
        side_effect=RuntimeError("Simulated persist_index failure"),
    ):
        try:
            service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
        except Exception as exc:
            context.cov_error = exc


@when(
    "walk_and_index succeeds but persist_index and persist_status both fail on a fresh resource"
)
def step_persist_index_and_status_double_fault(context: Context) -> None:
    """Exercise lines 231-234, 236: persist_index fails, then persist_status
    also fails when trying to record ERROR.

    The initial persist_status(INDEXING) at line 147 must succeed, then
    persist_index fails at line 218, then persist_status(ERROR) at line 221
    also fails, triggering lines 231-234 (inner except with warning log).
    """
    service: RepoIndexingService = context.cov_service
    _install_log_capture(context)

    original_persist_status = svc_mod.persist_status
    call_count = {"n": 0}

    def _persist_status_fail_on_error(*args, **kwargs):
        call_count["n"] += 1
        status_arg = kwargs.get("status")
        if status_arg == IndexStatus.ERROR:
            raise RuntimeError("Simulated persist_status failure on ERROR")
        return original_persist_status(*args, **kwargs)

    with (
        patch.object(
            svc_mod,
            "persist_index",
            side_effect=RuntimeError("Simulated persist_index failure"),
        ),
        patch.object(
            svc_mod,
            "persist_status",
            side_effect=_persist_status_fail_on_error,
        ),
    ):
        try:
            service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
        except Exception as exc:
            context.cov_error = exc


@when("persist_index is mocked to fail during re-index of the existing resource")
def step_persist_index_fails_reindex(context: Context) -> None:
    """Exercise lines 219-236 with has_good_index=True.

    When a good index already exists and persist_index raises, the service
    skips the persist_status(ERROR) call (the else branch is not taken for
    the outer if, and line 236 re-raises directly).
    """
    service: RepoIndexingService = context.cov_service

    with patch.object(
        svc_mod,
        "persist_index",
        side_effect=RuntimeError("Simulated persist_index failure"),
    ):
        try:
            service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
        except Exception as exc:
            context.cov_error = exc


@when("persist_index is mocked to fail during refresh")
def step_persist_index_fails_refresh(context: Context) -> None:
    """Exercise lines 367-368+: persist_index fails during refresh_index.

    The service should log a warning about failing to persist the
    refreshed index, then re-raise, preserving the previous good index.
    """
    service: RepoIndexingService = context.cov_service
    _install_log_capture(context)

    with patch.object(
        svc_mod,
        "persist_index",
        side_effect=RuntimeError("Simulated persist_index failure"),
    ):
        try:
            service.refresh_index(_RESOURCE_ID, context.cov_tmpdir)
        except Exception as exc:
            context.cov_error = exc


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


@then("a coverage-boost error should have been raised")
def step_error_raised(context: Context) -> None:
    assert context.cov_error is not None, "Expected an error but none was raised"


@then(
    "the coverage-boost double-fault warning about walk failure should have been logged"
)
def step_walk_double_fault_warning(context: Context) -> None:
    """Verify the warning from lines 183-185 was logged."""
    assert any(
        "Failed to persist ERROR status after walk failure" in msg
        for msg in context.cov_logged_warnings
    ), f"Expected walk double-fault warning in logs. Got: {context.cov_logged_warnings}"


@then('the coverage-boost persisted status should be "{status}"')
def step_persisted_status(context: Context, status: str) -> None:
    service: RepoIndexingService = context.cov_service
    meta = service.get_index_status(_RESOURCE_ID)
    assert meta is not None, "Expected persisted metadata, got None"
    actual = meta.status.value
    assert actual == status, f"Expected status '{status}', got '{actual}'"


@then(
    "the coverage-boost double-fault warning about persist failure should have been logged"
)
def step_persist_double_fault_warning(context: Context) -> None:
    """Verify the warning from lines 232-234 was logged."""
    assert any(
        "Failed to persist ERROR status after persist failure" in msg
        for msg in context.cov_logged_warnings
    ), (
        f"Expected persist double-fault warning in logs. Got: {context.cov_logged_warnings}"
    )


@then('the coverage-boost persisted status should still be "{status}"')
def step_persisted_status_still(context: Context, status: str) -> None:
    """Verify the previously good index was NOT overwritten with ERROR."""
    service: RepoIndexingService = context.cov_service
    meta = service.get_index_status(_RESOURCE_ID)
    assert meta is not None, "Expected persisted metadata, got None"
    actual = meta.status.value
    assert actual == status, f"Expected status '{status}', got '{actual}'"


@then("the coverage-boost refresh persist warning should have been logged")
def step_refresh_persist_warning(context: Context) -> None:
    """Verify the warning from lines 368-374 was logged."""
    assert any(
        "Failed to persist refreshed index" in msg
        for msg in context.cov_logged_warnings
    ), f"Expected refresh persist warning in logs. Got: {context.cov_logged_warnings}"
