"""Parametrized ``rule_store`` fixture for E.2.14b.

The conformance contract from ``development/TTP_TAGGING.md`` §E.2.14b:
both backends — :class:`FilesystemRuleStore` and
:class:`DatabaseRuleStore` — must satisfy the same observable
behavior. Tests that consume :func:`rule_store` are run twice, once
per backend.

Filesystem is skipped on non-Linux (it raises ``RuntimeError`` from
``__init__`` on macOS / Windows because the inotify dep is
Linux-only).
"""
from __future__ import annotations

import sys
from pathlib import Path
from typing import AsyncIterator

import pytest
import pytest_asyncio

from decnet.ttp.store.base import RuleStore
from decnet.ttp.store.impl.database import DatabaseRuleStore
from decnet.ttp.store.impl.filesystem import FilesystemRuleStore
from decnet.web.db.models import TTPRule


async def _seed_rule_filesystem(
    store: FilesystemRuleStore, rule_id: str, yaml_text: str,
) -> None:
    rules_dir: Path = store._rules_dir
    rules_dir.mkdir(parents=True, exist_ok=True)
    (rules_dir / f"{rule_id}.yaml").write_text(yaml_text, encoding="utf-8")


async def _seed_rule_database(
    store: DatabaseRuleStore, rule_id: str, yaml_text: str,
) -> None:
    # Direct ``ttp_rule`` insert — bypass the master sync helper to
    # keep tests deterministic. Mirrors what a swarm master would have
    # written into the table.
    repo = await store._ensure_repo()
    async with repo._session() as session:  # type: ignore[attr-defined]
        from datetime import datetime, timezone  # noqa: PLC0415

        session.add(TTPRule(
            rule_id=rule_id,
            rule_version=1,
            source_path=f"./rules/ttp/{rule_id}.yaml",
            yaml_content=yaml_text,
            updated_at=datetime.now(timezone.utc),
            updated_by="test",
        ))
        await session.commit()


async def seed_rule(store: RuleStore, rule_id: str, yaml_text: str) -> None:
    """Backend-aware test helper: write a rule into the store.

    Filesystem store: drop a YAML file under ``_rules_dir``.
    Database store: insert a ``ttp_rule`` row directly.
    """
    if isinstance(store, FilesystemRuleStore):
        await _seed_rule_filesystem(store, rule_id, yaml_text)
    elif isinstance(store, DatabaseRuleStore):
        await _seed_rule_database(store, rule_id, yaml_text)
    else:  # pragma: no cover
        raise TypeError(f"unknown rule store backend: {type(store).__name__}")


@pytest_asyncio.fixture(
    params=["filesystem", "database"],
    ids=["filesystem", "database"],
)
async def rule_store(
    request: pytest.FixtureRequest, tmp_path: Path,
) -> AsyncIterator[RuleStore]:
    """Yield a fresh :class:`RuleStore` instance per parametrization.

    The filesystem backend is constructed against a ``tmp_path``
    rules dir so tests never touch the real ``./rules/``. The
    database backend gets a per-test SQLite repo (initialized with
    ``metadata.create_all``) so each test sees an empty
    ``ttp_rule`` / ``ttp_rule_state`` pair.
    """
    backend = request.param
    if backend == "filesystem":
        if sys.platform != "linux":
            pytest.skip("FilesystemRuleStore requires Linux (inotify)")
        yield FilesystemRuleStore(rules_dir=tmp_path)
    else:
        from decnet.web.db.sqlite.repository import SQLiteRepository  # noqa: PLC0415

        repo = SQLiteRepository(db_path=str(tmp_path / "ttp_store.db"))
        await repo.initialize()
        store = DatabaseRuleStore(repo=repo)
        # Mirror FS store's ``_rules_dir`` attr so cross-backend tests
        # that need to drop sample YAML on disk have somewhere to put
        # it; the DB-backend tests that need rule definitions either
        # write to ``ttp_rule`` directly or call ``upsert_rule``.
        store._rules_dir = tmp_path  # type: ignore[attr-defined]
        try:
            yield store
        finally:
            try:
                await repo.engine.dispose()
            except Exception:  # noqa: BLE001 — teardown best-effort
                pass
