"""Tests for the unified color-strip stream-builder registry."""

from __future__ import annotations

import pytest

from ledgrab.core.processing import color_strip_kinds
from ledgrab.core.processing.color_strip_kinds import (
    SHARABLE_KINDS,
    STREAM_BUILDERS,
    StreamDeps,
    build_stream,
)
from ledgrab.storage.color_strip_source import _SOURCE_TYPE_MAP


def test_stream_builders_partition_storage_kinds_with_sharable():
    """STREAM_BUILDERS ∪ SHARABLE_KINDS == storage source_types."""
    storage_kinds = set(_SOURCE_TYPE_MAP.keys())
    covered = set(STREAM_BUILDERS.keys()) | SHARABLE_KINDS
    assert (
        storage_kinds == covered
    ), f"missing={storage_kinds - covered}, extra={covered - storage_kinds}"


def test_sharable_kinds_list_matches_actual_sharable_property():
    """The hand-coded SHARABLE_KINDS list mirrors the `sharable` @property values.

    Every ColorStripSource subclass currently implements ``sharable`` as a
    literal ``return True``/``return False`` that does not touch ``self``,
    so ``fget(None)`` is a safe introspection. If a future subclass changes
    that contract — e.g. by computing sharability from instance state — this
    test will raise a clear error rather than silently misclassify the kind,
    forcing the maintainer to update both ``SHARABLE_KINDS`` and this test.
    """

    def _is_sharable(name: str, cls) -> bool:
        prop = getattr(cls, "sharable", None)
        if not isinstance(prop, property) or prop.fget is None:
            raise AssertionError(
                f"{cls.__name__} (source_type={name!r}) does not expose `sharable` "
                f"as a @property; SHARABLE_KINDS introspection no longer works."
            )
        try:
            return bool(prop.fget(None))
        except (AttributeError, TypeError) as e:
            raise AssertionError(
                f"{cls.__name__}.sharable now depends on `self` ({e}); update "
                "this test and SHARABLE_KINDS together."
            ) from e

    actually_sharable = {name for name, cls in _SOURCE_TYPE_MAP.items() if _is_sharable(name, cls)}
    assert actually_sharable == set(SHARABLE_KINDS), (
        "SHARABLE_KINDS drifted from the @property values: "
        f"actually_sharable={sorted(actually_sharable)}, "
        f"SHARABLE_KINDS={sorted(SHARABLE_KINDS)}"
    )


def test_build_stream_raises_on_unknown_kind():
    class _FakeSource:
        source_type = "totally_unregistered_kind"
        id = "fake"

    deps = StreamDeps(css_manager=None)
    with pytest.raises(ValueError, match="totally_unregistered_kind"):
        build_stream(_FakeSource(), deps)


def test_coverage_assertion_raises_on_drift(monkeypatch):
    """Adding a kind to storage's _SOURCE_TYPE_MAP without a builder fails fast."""

    monkeypatch.setitem(_SOURCE_TYPE_MAP, "phantom", object)
    with pytest.raises(RuntimeError, match="phantom"):
        color_strip_kinds._assert_stream_kind_coverage()


def test_legacy_static_alias_resolves_to_single_color_builder():
    """The legacy 'static' source_type still maps to the single_color builder."""
    assert STREAM_BUILDERS["static"] is not None
    assert STREAM_BUILDERS["single_color"] is not None
    # The two route to the same loader, but builders are distinct closures —
    # call them with a tiny source stub and confirm same class.
    from ledgrab.core.processing.color_strip_stream import SingleColorStripStream
    from ledgrab.storage.color_strip_source import SingleColorStripSource
    from datetime import datetime, timezone

    src = SingleColorStripSource(
        id="t",
        name="t",
        source_type="static",
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )
    deps = StreamDeps(css_manager=None)
    s = STREAM_BUILDERS["static"](src, deps)
    assert isinstance(s, SingleColorStripStream)
