"""Step definitions for skill add persistence (issue #620).

Verifies that ``SkillService`` with a ``SkillRepository`` correctly
persists skills to the database so that a fresh service instance sees
the previously added skills.
"""

from __future__ import annotations

import os
import tempfile
from typing import Any

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

from cleveragents.application.services.skill_service import SkillService
from cleveragents.infrastructure.database.models import Base
from cleveragents.infrastructure.database.repositories import (
    SkillRepository,
)
from cleveragents.skills.schema import SkillConfigSchema

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

_SKILL_YAML_TEMPLATE = """\
name: {name}
description: "{name} test skill"
tools:
  - name: builtin/read_file
"""


def _enable_fk(dbapi_conn: Any, _rec: Any) -> None:
    """Enable SQLite foreign key enforcement."""
    cursor = dbapi_conn.cursor()
    cursor.execute("PRAGMA foreign_keys=ON")
    cursor.close()


def _make_service(
    factory: Any,
) -> SkillService:
    """Build a SkillService backed by the given session factory."""
    repo = SkillRepository(session_factory=factory)
    return SkillService(skill_repo=repo, session_factory=factory)


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


@given("a fresh skill service backed by an in-memory database")
def step_fresh_persistent_service(context: Context) -> None:
    engine = create_engine("sqlite:///:memory:", echo=False)
    event.listen(engine, "connect", _enable_fk)
    Base.metadata.create_all(engine)

    shared_session = sessionmaker(bind=engine)()

    def _sf() -> Session:
        return shared_session

    context.sap_engine = engine
    context.sap_session_factory = _sf
    context.sap_service = _make_service(context.sap_session_factory)
    context.sap_yaml_paths = []
    context.sap_configs = []
    context.sap_listed = []
    context.sap_error = None


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


@given('a skill config YAML for "{name}" at a temp path')
def step_skill_config_yaml(context: Context, name: str) -> None:
    content = _SKILL_YAML_TEMPLATE.format(name=name)
    fd, path = tempfile.mkstemp(suffix=".yaml")
    with os.fdopen(fd, "w") as fh:
        fh.write(content)
    context.sap_yaml_paths.append(path)
    schema = SkillConfigSchema.from_yaml_file(path)
    context.sap_configs.append(schema)


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


@when("I add the skill via the persistent service")
def step_add_skill_persistent(context: Context) -> None:
    config = context.sap_configs[-1]
    path = context.sap_yaml_paths[-1]
    context.sap_service.add_skill(config, config_path=path)


@when("I add all pending skill configs via the persistent service")
def step_add_all_skills_persistent(context: Context) -> None:
    for cfg, path in zip(context.sap_configs, context.sap_yaml_paths, strict=True):
        context.sap_service.add_skill(cfg, config_path=path)


@when("I create a second service instance from the same database")
def step_second_service(context: Context) -> None:
    context.sap_service2 = _make_service(context.sap_session_factory)


@when("I list skills from the second service instance")
def step_list_from_second(context: Context) -> None:
    context.sap_listed = context.sap_service2.list_skills()


@when('I remove the skill "{name}" via the persistent service')
def step_remove_skill_persistent(context: Context, name: str) -> None:
    context.sap_service.remove_skill(name)


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


@then('the listed persistent skills should contain "{name}"')
def step_listed_contains(context: Context, name: str) -> None:
    names = [s.name for s in context.sap_listed]
    assert name in names, f"{name} not in {names}"


@then('the listed persistent skills should not contain "{name}"')
def step_listed_not_contains(context: Context, name: str) -> None:
    names = [s.name for s in context.sap_listed]
    assert name not in names, f"{name} unexpectedly in {names}"


@then("the listed persistent skills count should be {count:d}")
def step_listed_count(context: Context, count: int) -> None:
    actual = len(context.sap_listed)
    assert actual == count, f"Expected {count}, got {actual}"


@then('adding a duplicate "{name}" should raise ValueError')
def step_duplicate_raises(context: Context, name: str) -> None:
    config = context.sap_configs[-1]
    try:
        context.sap_service.add_skill(config)
        raise AssertionError("Expected ValueError for duplicate skill")
    except ValueError as exc:
        assert "already registered" in str(exc)
