"""Tests for AudioProcessingTemplateStore — CRUD, template expansion, and cycle detection."""

import pytest

from ledgrab.core.filters.filter_instance import FilterInstance
from ledgrab.storage.audio_processing_template_store import AudioProcessingTemplateStore
from ledgrab.storage.database import Database

# Ensure all built-in audio filters are registered
import ledgrab.core.audio.filters  # noqa: F401


@pytest.fixture
def apt_store(tmp_path):
    """Provide an AudioProcessingTemplateStore backed by a temp database."""
    db = Database(tmp_path / "test.db")
    store = AudioProcessingTemplateStore(db)
    yield store
    db.close()


# ---------------------------------------------------------------------------
# CRUD
# ---------------------------------------------------------------------------


class TestCRUD:
    def test_create_and_get(self, apt_store):
        t = apt_store.create_template(
            name="My Template",
            filters=[FilterInstance("gain", {"factor": 2.0})],
            description="test desc",
            tags=["audio"],
        )
        assert t.id.startswith("apt_")
        assert t.name == "My Template"
        assert t.description == "test desc"
        assert t.tags == ["audio"]
        assert len(t.filters) == 1
        assert t.filters[0].filter_id == "gain"

        fetched = apt_store.get_template(t.id)
        assert fetched.name == "My Template"

    def test_create_empty_filters(self, apt_store):
        t = apt_store.create_template(name="Empty")
        assert t.filters == []

    def test_create_duplicate_name_raises(self, apt_store):
        apt_store.create_template(name="UniqueA")
        with pytest.raises(ValueError, match="already exists"):
            apt_store.create_template(name="UniqueA")

    def test_create_unknown_filter_raises(self, apt_store):
        with pytest.raises(ValueError, match="Unknown audio filter type"):
            apt_store.create_template(name="Bad", filters=[FilterInstance("nonexistent", {})])

    def test_get_all(self, apt_store):
        apt_store.create_template(name="T1")
        apt_store.create_template(name="T2")
        all_templates = apt_store.get_all_templates()
        names = {t.name for t in all_templates}
        assert "T1" in names
        assert "T2" in names

    def test_update_name_and_filters(self, apt_store):
        t = apt_store.create_template(name="Original")
        updated = apt_store.update_template(
            t.id,
            name="Renamed",
            filters=[FilterInstance("gain", {"factor": 3.0})],
        )
        assert updated.name == "Renamed"
        assert len(updated.filters) == 1
        assert updated.filters[0].options["factor"] == 3.0
        assert updated.updated_at > t.created_at

    def test_update_nonexistent_raises(self, apt_store):
        with pytest.raises(ValueError):
            apt_store.update_template("apt_nonexistent", name="X")

    def test_update_unknown_filter_raises(self, apt_store):
        t = apt_store.create_template(name="Valid")
        with pytest.raises(ValueError, match="Unknown audio filter type"):
            apt_store.update_template(t.id, filters=[FilterInstance("nonexistent", {})])

    def test_delete(self, apt_store):
        t = apt_store.create_template(name="ToDelete")
        apt_store.delete_template(t.id)
        with pytest.raises(ValueError):
            apt_store.get_template(t.id)

    def test_delete_nonexistent_raises(self, apt_store):
        with pytest.raises(ValueError):
            apt_store.delete_template("apt_nonexistent")

    def test_persistence_across_reload(self, tmp_path):
        """Templates survive store reconstruction (SQLite persistence)."""
        db = Database(tmp_path / "persist.db")
        store1 = AudioProcessingTemplateStore(db)
        t = store1.create_template(
            name="Persistent",
            filters=[FilterInstance("gain", {"factor": 1.5})],
        )
        tid = t.id
        db.close()

        db2 = Database(tmp_path / "persist.db")
        store2 = AudioProcessingTemplateStore(db2)
        reloaded = store2.get_template(tid)
        assert reloaded.name == "Persistent"
        assert reloaded.filters[0].filter_id == "gain"
        db2.close()


# ---------------------------------------------------------------------------
# Template composition (audio_filter_template)
# ---------------------------------------------------------------------------


class TestTemplateComposition:
    def test_resolve_flat_filters(self, apt_store):
        """Non-template filters pass through unchanged."""
        filters = [
            FilterInstance("gain", {"factor": 2.0}),
            FilterInstance("inverter", {}),
        ]
        resolved = apt_store.resolve_filter_instances(filters)
        assert len(resolved) == 2
        assert resolved[0].filter_id == "gain"
        assert resolved[1].filter_id == "inverter"

    def test_resolve_nested_template(self, apt_store):
        """audio_filter_template reference is expanded recursively."""
        inner = apt_store.create_template(
            name="Inner",
            filters=[FilterInstance("gain", {"factor": 3.0})],
        )
        outer_filters = [
            FilterInstance("inverter", {}),
            FilterInstance("audio_filter_template", {"template_id": inner.id}),
        ]
        resolved = apt_store.resolve_filter_instances(outer_filters)
        assert len(resolved) == 2
        assert resolved[0].filter_id == "inverter"
        assert resolved[1].filter_id == "gain"

    def test_resolve_missing_template_skipped(self, apt_store):
        """References to nonexistent templates are silently skipped."""
        filters = [
            FilterInstance("gain", {}),
            FilterInstance("audio_filter_template", {"template_id": "apt_nonexistent"}),
        ]
        resolved = apt_store.resolve_filter_instances(filters)
        assert len(resolved) == 1
        assert resolved[0].filter_id == "gain"

    def test_resolve_cycle_detection(self, apt_store):
        """Cycles in template composition are detected and broken."""
        t1 = apt_store.create_template(
            name="A",
            filters=[FilterInstance("gain", {})],
        )
        t2 = apt_store.create_template(
            name="B",
            filters=[FilterInstance("audio_filter_template", {"template_id": t1.id})],
        )
        # Manually create a cycle: A references B
        apt_store.update_template(
            t1.id,
            filters=[FilterInstance("audio_filter_template", {"template_id": t2.id})],
        )
        # Resolving should not infinite-loop; the cyclic reference is skipped
        resolved = apt_store.resolve_filter_instances(t1.filters)
        # Only gain from B's expansion of A (which itself is skipped due to cycle)
        # The cycle is broken: A -> B -> A(skipped) -> gain never reached
        # Actually: A has [ref to B], B has [ref to A]. Resolving A:
        #   - Visit A, expand B -> B has [ref to A], but A is already visited -> skip
        # So result is empty.
        assert len(resolved) == 0
