"""
Tests for FigureStrategy._build_pre_payload() and build() callback flow.
"""

from typing import Any

import polars as pl

from orama.color import ColorAssignerByCategory, ColorSelectorStrategy
from orama.strategies.base import FigureStrategy, FigureView

_DFS: dict[str, pl.DataFrame] = {"main": pl.DataFrame()}


class ConcreteStrategy(FigureStrategy):
    """Minimal concrete strategy for testing build() and _build_pre_payload()."""

    @classmethod
    def supported_color_selectors(cls) -> list[type]:
        return [ColorSelectorStrategy]

    @classmethod
    def supported_color_assigners(cls) -> list[type]:
        return [ColorAssignerByCategory]

    def get_query_params(self) -> dict[str, Any]:
        return {}

    def _render_from_payload(self, payload: dict[str, Any]) -> Any:
        import plotly.graph_objects as go

        fig = go.Figure()
        if payload.get("title"):
            self._apply_title(fig, payload["title"], payload.get("subtitle"))
        return fig

    def _get_schema_fields(self) -> dict[str, type[pl.DataType]]:
        return {}


def _make(
    title: str | None = None,
    subtitle: str | None = None,
    caption: str | None = None,
    description: str | None = None,
    storytelling_context: str | None = None,
    pre_build_callback: Any = None,
    post_build_callback: Any = None,
) -> ConcreteStrategy:
    return ConcreteStrategy(
        fields_metadata={},
        title=title,
        subtitle=subtitle,
        caption=caption,
        description=description,
        storytelling_context=storytelling_context,
        pre_build_callback=pre_build_callback,
        post_build_callback=post_build_callback,
    )


def test_pre_payload_is_flat() -> None:
    s = _make(title="T", subtitle="S", caption="C", description="D")
    pre = s._build_pre_payload(_DFS)
    assert "metadata" not in pre
    assert pre["title"] == "T"
    assert pre["subtitle"] == "S"
    assert pre["caption"] == "C"
    assert pre["description"] == "D"


def test_pre_payload_strategy_key() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert pre["strategy"] == "ConcreteStrategy"


def test_pre_payload_dfs_is_copy_of_input() -> None:
    df = pl.DataFrame({"x": [1]})
    s = _make()
    pre = s._build_pre_payload({"main": df})
    assert "main" in pre["dfs"]


def test_pre_payload_none_fields() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert pre["title"] is None
    assert pre["description"] is None


def test_pre_payload_includes_storytelling_context() -> None:
    s = _make(storytelling_context="Costs spiked in Q3 due to supply constraints.")
    pre = s._build_pre_payload(_DFS)
    assert pre["storytelling_context"] == "Costs spiked in Q3 due to supply constraints."


def test_pre_payload_storytelling_context_default_none() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert pre["storytelling_context"] is None


def test_base_metadata_includes_storytelling_context() -> None:
    s = _make(storytelling_context="Trend shows steady growth.")
    meta = s._build_base_metadata()
    assert "storytelling_context" in meta
    assert meta["storytelling_context"] == "Trend shows steady growth."


def test_pre_payload_color_rules_is_list() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert "color_rules" in pre
    assert isinstance(pre["color_rules"], list)


def test_pre_payload_variables_is_empty_for_no_variable_strategy() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert pre["variables"] == {}


def test_pre_payload_options_is_empty_for_no_option_strategy() -> None:
    s = _make()
    pre = s._build_pre_payload(_DFS)
    assert pre["options"] == {}


def test_build_no_callbacks_returns_figure_view(tr: Any, theme: Any) -> None:
    s = _make(caption="Cap", description="Desc", storytelling_context="Story here.")
    view = s.build(_DFS, tr, theme)
    assert isinstance(view, FigureView)
    assert view.caption == "Cap"
    assert view.description == "Desc"
    assert view.storytelling_context == "Story here."


def test_pre_callback_title_propagates_to_figure(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["title"] = "Overridden"

    s = _make(title="Original", pre_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.figure.layout.title.text == "Overridden"


def test_pre_callback_description_propagates_to_view(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["description"] = "From callback"

    s = _make(description="Original", pre_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.description == "From callback"


def test_pre_callback_caption_propagates_to_view(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["caption"] = "New caption"

    s = _make(caption="Old caption", pre_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.caption == "New caption"


def test_pre_callback_subtitle_propagates_to_figure(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["title"] = "T"
        payload["subtitle"] = "Sub"

    s = _make(pre_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.figure.layout.title.subtitle.text == "Sub"


def test_pre_callback_dfs_mutation_is_used(tr: Any, theme: Any) -> None:
    """Mutations to payload["dfs"] during the callback are visible in _render_from_payload."""
    received: dict[str, Any] = {}

    def cb(payload: dict[str, Any]) -> None:
        payload["dfs"]["main"] = pl.DataFrame({"injected": [99]})

    class CaptureDfs(ConcreteStrategy):
        """Captures the payload passed to _render_from_payload() for assertion."""

        def _render_from_payload(self, payload: dict[str, Any]) -> Any:
            received["dfs"] = payload["dfs"]
            return super()._render_from_payload(payload)

    s = CaptureDfs(fields_metadata={}, pre_build_callback=cb)
    s.build({"main": pl.DataFrame()}, tr, theme)
    assert "injected" in received["dfs"]["main"].columns


def test_pre_callback_storytelling_context_propagates_to_view(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["storytelling_context"] = "From callback"

    s = _make(storytelling_context="Original", pre_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.storytelling_context == "From callback"


def test_pre_callback_does_not_mutate_instance(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["title"] = "Overridden"
        payload["description"] = "New desc"
        payload["storytelling_context"] = "Changed"

    s = _make(title="Original", description="Old", storytelling_context="Unchanged", pre_build_callback=cb)
    s.build(_DFS, tr, theme)
    assert s._title == "Original"
    assert s._description == "Old"
    assert s._storytelling_context == "Unchanged"


def test_post_callback_description_overrides_view(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["description"] = "Post desc"

    s = _make(description="Pre desc", post_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.description == "Post desc"


def test_post_callback_caption_overrides_view(tr: Any, theme: Any) -> None:
    def cb(payload: dict[str, Any]) -> None:
        payload["caption"] = "Post cap"

    s = _make(caption="Pre cap", post_build_callback=cb)
    view = s.build(_DFS, tr, theme)
    assert view.caption == "Post cap"


def test_both_callbacks_post_wins_over_pre(tr: Any, theme: Any) -> None:
    def pre(payload: dict[str, Any]) -> None:
        payload["description"] = "From pre"

    def post(payload: dict[str, Any]) -> None:
        payload["description"] = "From post"

    s = _make(pre_build_callback=pre, post_build_callback=post)
    view = s.build(_DFS, tr, theme)
    assert view.description == "From post"


def test_plot_uses_instance_title(tr: Any, theme: Any) -> None:
    s = _make(title="My Title", caption="Cap", description="Desc")
    view = s.plot(_DFS, tr, theme)
    assert view.figure.layout.title.text == "My Title"
    assert view.caption == "Cap"
    assert view.description == "Desc"
