"""Tests for the ComponentBuilder fluent builder API.

Covers every builder method, chaining, build/build_single, type fields,
and optional parameter passthrough.
"""

import pytest

from dexfather.components import ComponentBuilder


# ── Core components ─────────────────────────────────────────────


class TestHeader:
    def test_header_basic(self):
        result = ComponentBuilder().header("Hello").build()
        assert len(result) == 1
        assert result[0]["type"] == "header"
        assert result[0]["text"] == "Hello"
        assert "size" not in result[0]

    def test_header_with_size(self):
        result = ComponentBuilder().header("Title", size="lg").build()
        assert result[0]["size"] == "lg"

    def test_header_build_single(self):
        comp = ComponentBuilder().header("Only one").build_single()
        assert comp["type"] == "header"
        assert comp["text"] == "Only one"


class TestSection:
    def test_section_basic(self):
        result = ComponentBuilder().section("Body text").build()
        assert result[0]["type"] == "section"
        assert result[0]["text"] == "Body text"
        assert "title" not in result[0]
        assert "accessory" not in result[0]

    def test_section_with_title(self):
        result = ComponentBuilder().section("Body", title="My Title").build()
        assert result[0]["title"] == "My Title"

    def test_section_with_accessory(self):
        acc = {"type": "button", "text": "Click", "value": "click_val"}
        result = ComponentBuilder().section("Body", accessory=acc).build()
        assert result[0]["accessory"] == acc


class TestDivider:
    def test_divider(self):
        result = ComponentBuilder().divider().build()
        assert result[0] == {"type": "divider"}


class TestContext:
    def test_context(self):
        elements = [
            {"type": "text", "text": "Hello"},
            {"type": "image", "image_url": "https://example.com/img.png"},
        ]
        result = ComponentBuilder().context(elements).build()
        assert result[0]["type"] == "context"
        assert result[0]["elements"] == elements


class TestActions:
    def test_actions(self):
        buttons = [
            {"text": "OK", "value": "ok", "style": "primary"},
            {"text": "Cancel", "value": "cancel"},
        ]
        result = ComponentBuilder().actions(buttons).build()
        assert result[0]["type"] == "actions"
        elements = result[0]["elements"]
        assert len(elements) == 2
        # Each element should have type=button added
        assert elements[0]["type"] == "button"
        assert elements[0]["text"] == "OK"
        assert elements[0]["value"] == "ok"
        assert elements[0]["style"] == "primary"
        assert elements[1]["type"] == "button"


class TestImage:
    def test_image_basic(self):
        result = ComponentBuilder().image("https://example.com/pic.jpg").build()
        assert result[0]["type"] == "image"
        assert result[0]["image_url"] == "https://example.com/pic.jpg"
        assert "alt_text" not in result[0]

    def test_image_with_options(self):
        result = ComponentBuilder().image(
            "https://example.com/pic.jpg",
            alt_text="A nice photo",
            title="Photo Title",
        ).build()
        assert result[0]["alt_text"] == "A nice photo"
        assert result[0]["title"] == "Photo Title"


class TestAlert:
    def test_alert_basic(self):
        result = ComponentBuilder().alert("error", "Something broke").build()
        assert result[0]["type"] == "alert"
        assert result[0]["alert_type"] == "error"
        assert result[0]["title"] == "Something broke"
        assert "description" not in result[0]

    def test_alert_with_description(self):
        result = ComponentBuilder().alert("info", "Note", "Read this carefully").build()
        assert result[0]["description"] == "Read this carefully"

    def test_alert_types(self):
        for t in ("info", "warning", "success", "error"):
            result = ComponentBuilder().alert(t, "T").build()
            assert result[0]["alert_type"] == t


class TestProgress:
    def test_progress_basic(self):
        result = ComponentBuilder().progress(75.0).build()
        assert result[0]["type"] == "progress"
        assert result[0]["value"] == 75.0
        assert "label" not in result[0]
        assert "show_percentage" not in result[0]

    def test_progress_with_options(self):
        result = ComponentBuilder().progress(
            42.5, label="Loading...", show_percentage=True
        ).build()
        assert result[0]["label"] == "Loading..."
        assert result[0]["show_percentage"] is True


class TestStats:
    def test_stats_basic(self):
        items = [
            {"label": "SOL", "value": "$142.50"},
            {"label": "BONK", "value": "$0.00002", "change": "-1.1%", "change_type": "negative"},
        ]
        result = ComponentBuilder().stats(items).build()
        assert result[0]["type"] == "stats"
        assert result[0]["items"] == items
        assert "columns" not in result[0]

    def test_stats_with_columns(self):
        items = [{"label": "A", "value": "1"}]
        result = ComponentBuilder().stats(items, columns=3).build()
        assert result[0]["columns"] == 3


# ── Table & Advanced UI ────────────────────────────────────────


class TestTable:
    def test_table_basic(self):
        headers = [{"key": "name", "label": "Name"}, {"key": "price", "label": "Price", "sortable": True}]
        rows = [{"name": "SOL", "price": "$142"}, {"name": "ETH", "price": "$3200"}]
        result = ComponentBuilder().table(headers, rows).build()
        assert result[0]["type"] == "table"
        assert result[0]["headers"] == headers
        assert result[0]["rows"] == rows

    def test_table_with_pagination(self):
        result = ComponentBuilder().table(
            [{"key": "x", "label": "X"}],
            [{"x": "1"}],
            page_size=10,
            page=2,
        ).build()
        assert result[0]["page_size"] == 10
        assert result[0]["page"] == 2


class TestTabs:
    def test_tabs_basic(self):
        tabs = [
            {"id": "t1", "label": "Tab 1", "content": "Content 1"},
            {"id": "t2", "label": "Tab 2", "content": "Content 2"},
        ]
        result = ComponentBuilder().tabs(tabs).build()
        assert result[0]["type"] == "tabs"
        assert result[0]["tabs"] == tabs

    def test_tabs_with_default(self):
        tabs = [{"id": "t1", "label": "T1", "content": "C1"}]
        result = ComponentBuilder().tabs(tabs, default_tab="t1").build()
        assert result[0]["default_tab"] == "t1"


class TestAccordion:
    def test_accordion(self):
        sections = [
            {"id": "s1", "title": "FAQ 1", "content": "Answer 1", "default_open": True},
            {"id": "s2", "title": "FAQ 2", "content": "Answer 2"},
        ]
        result = ComponentBuilder().accordion(sections).build()
        assert result[0]["type"] == "accordion"
        assert result[0]["sections"] == sections


class TestCarousel:
    def test_carousel(self):
        items = [
            {"title": "Item 1", "description": "Desc 1", "image_url": "https://example.com/1.jpg"},
            {"title": "Item 2", "action": {"text": "View", "value": "view_2"}},
        ]
        result = ComponentBuilder().carousel(items).build()
        assert result[0]["type"] == "carousel"
        assert result[0]["items"] == items


class TestUserCard:
    def test_user_card_basic(self):
        result = ComponentBuilder().user_card("Alice").build()
        assert result[0]["type"] == "user_card"
        assert result[0]["name"] == "Alice"
        assert "username" not in result[0]

    def test_user_card_full(self):
        stats = [{"label": "Posts", "value": "42"}, {"label": "Followers", "value": "1.2k"}]
        result = ComponentBuilder().user_card(
            "Alice",
            username="@alice",
            avatar_url="https://example.com/alice.jpg",
            stats=stats,
            action_label="Follow",
            action_value="follow_alice",
        ).build()
        c = result[0]
        assert c["username"] == "@alice"
        assert c["avatar_url"] == "https://example.com/alice.jpg"
        assert c["stats"] == stats
        assert c["action_label"] == "Follow"
        assert c["action_value"] == "follow_alice"


# ── DeFi components ────────────────────────────────────────────


class TestTokenTable:
    def test_token_table_basic(self):
        tokens = [{"name": "Solana", "symbol": "SOL", "price": "$142.50"}]
        result = ComponentBuilder().token_table(tokens).build()
        assert result[0]["type"] == "token_table"
        assert result[0]["tokens"] == tokens

    def test_token_table_with_columns(self):
        tokens = [{"name": "SOL", "symbol": "SOL", "price": "$142"}]
        result = ComponentBuilder().token_table(tokens, columns=["name", "price"]).build()
        assert result[0]["columns"] == ["name", "price"]


class TestTokenCard:
    def test_token_card_basic(self):
        result = ComponentBuilder().token_card("Solana", "SOL", "$142.50").build()
        c = result[0]
        assert c["type"] == "token_card"
        assert c["name"] == "Solana"
        assert c["symbol"] == "SOL"
        assert c["price"] == "$142.50"

    def test_token_card_full(self):
        result = ComponentBuilder().token_card(
            "Solana", "SOL", "$142.50",
            logo_url="https://example.com/sol.png",
            change_24h="+5.2%",
            market_cap="$50B",
            volume_24h="$2B",
            holders=1000000,
            mint="So11111111111111111111111111111111111111112",
            buy_label="Buy SOL",
            sell_label="Sell SOL",
        ).build()
        c = result[0]
        assert c["logo_url"] == "https://example.com/sol.png"
        assert c["change_24h"] == "+5.2%"
        assert c["market_cap"] == "$50B"
        assert c["volume_24h"] == "$2B"
        assert c["holders"] == 1000000
        assert c["mint"] == "So11111111111111111111111111111111111111112"
        assert c["buy_label"] == "Buy SOL"
        assert c["sell_label"] == "Sell SOL"


class TestPriceChart:
    def test_price_chart_basic(self):
        data = [{"t": 1700000000, "o": 100.0, "h": 105.0, "l": 99.0, "c": 103.0, "v": 5000.0}]
        result = ComponentBuilder().price_chart(data).build()
        assert result[0]["type"] == "price_chart"
        assert result[0]["data"] == data

    def test_price_chart_with_timeframes(self):
        data = [{"t": 1, "o": 1.0, "h": 2.0, "l": 0.5, "c": 1.5, "v": 100.0}]
        result = ComponentBuilder().price_chart(data, timeframes=["1m", "5m", "1h"]).build()
        assert result[0]["timeframes"] == ["1m", "5m", "1h"]


class TestWalletAddress:
    def test_wallet_address_basic(self):
        addr = "So11111111111111111111111111111111111111112"
        result = ComponentBuilder().wallet_address(addr).build()
        assert result[0]["type"] == "wallet_address"
        assert result[0]["address"] == addr

    def test_wallet_address_with_options(self):
        result = ComponentBuilder().wallet_address(
            "0xabc123",
            chain="ethereum",
            label="Treasury Wallet",
        ).build()
        assert result[0]["chain"] == "ethereum"
        assert result[0]["label"] == "Treasury Wallet"


class TestLeaderboard:
    def test_leaderboard(self):
        entries = [
            {"rank": 1, "name": "Alice", "value": "$100k", "change": "+5%", "change_type": "positive"},
            {"rank": 2, "name": "Bob", "value": "$90k"},
        ]
        result = ComponentBuilder().leaderboard(entries).build()
        assert result[0]["type"] == "leaderboard"
        assert result[0]["entries"] == entries


# ── Form components ─────────────────────────────────────────────


class TestForm:
    def test_form_basic(self):
        elements = [
            {"type": "text_input", "name": "username", "label": "Username"},
        ]
        result = ComponentBuilder().form("form_1", elements).build()
        assert result[0]["type"] == "form"
        assert result[0]["form_id"] == "form_1"
        assert result[0]["elements"] == elements

    def test_form_with_submit_label(self):
        result = ComponentBuilder().form(
            "f1",
            [{"type": "text_input", "name": "x"}],
            submit_label="Send",
        ).build()
        assert result[0]["submit_label"] == "Send"


class TestTextInput:
    def test_text_input_basic(self):
        result = ComponentBuilder().text_input("email").build()
        assert result[0]["type"] == "text_input"
        assert result[0]["name"] == "email"

    def test_text_input_full(self):
        result = ComponentBuilder().text_input(
            "email",
            label="Email Address",
            placeholder="you@example.com",
            required=True,
            min_length=5,
            max_length=100,
        ).build()
        c = result[0]
        assert c["label"] == "Email Address"
        assert c["placeholder"] == "you@example.com"
        assert c["required"] is True
        assert c["min_length"] == 5
        assert c["max_length"] == 100


class TestSelectMenu:
    def test_select_menu_basic(self):
        options = [{"text": "Option A", "value": "a"}, {"text": "Option B", "value": "b"}]
        result = ComponentBuilder().select_menu("choice", options).build()
        assert result[0]["type"] == "select_menu"
        assert result[0]["name"] == "choice"
        assert result[0]["options"] == options

    def test_select_menu_with_options(self):
        options = [{"text": "A", "value": "a"}]
        result = ComponentBuilder().select_menu(
            "choice",
            options,
            label="Pick one",
            multi=True,
            placeholder="Select...",
        ).build()
        assert result[0]["label"] == "Pick one"
        assert result[0]["multi"] is True
        assert result[0]["placeholder"] == "Select..."


class TestCheckbox:
    def test_checkbox_basic(self):
        result = ComponentBuilder().checkbox("agree", "I agree to terms").build()
        assert result[0]["type"] == "checkbox"
        assert result[0]["name"] == "agree"
        assert result[0]["label"] == "I agree to terms"

    def test_checkbox_with_options(self):
        result = ComponentBuilder().checkbox(
            "agree",
            "I agree",
            description="You must agree to continue",
            initial_value=True,
        ).build()
        assert result[0]["description"] == "You must agree to continue"
        assert result[0]["initial_value"] is True


class TestTextarea:
    def test_textarea_basic(self):
        result = ComponentBuilder().textarea("bio").build()
        assert result[0]["type"] == "textarea"
        assert result[0]["name"] == "bio"

    def test_textarea_full(self):
        result = ComponentBuilder().textarea(
            "bio",
            label="Biography",
            placeholder="Tell us about yourself...",
            required=True,
            min_length=10,
            max_length=500,
        ).build()
        c = result[0]
        assert c["label"] == "Biography"
        assert c["placeholder"] == "Tell us about yourself..."
        assert c["required"] is True
        assert c["min_length"] == 10
        assert c["max_length"] == 500


# ── Live update components ──────────────────────────────────────


class TestLivePriceTicker:
    def test_live_price_ticker_basic(self):
        result = ComponentBuilder().live_price_ticker("SOL", 142.50, 5.23).build()
        assert result[0]["type"] == "live_price_ticker"
        assert result[0]["token"] == "SOL"
        assert result[0]["price"] == 142.50
        assert result[0]["change"] == 5.23

    def test_live_price_ticker_with_interval(self):
        result = ComponentBuilder().live_price_ticker("BTC", 60000.0, -2.1, interval=30).build()
        assert result[0]["interval"] == 30


class TestTaskProgress:
    def test_task_progress(self):
        tasks = [
            {"id": "t1", "label": "Fetch data", "status": "done", "result": "OK", "duration": "1.2s"},
            {"id": "t2", "label": "Process", "status": "running"},
            {"id": "t3", "label": "Save", "status": "pending"},
        ]
        result = ComponentBuilder().task_progress("Pipeline Status", tasks).build()
        assert result[0]["type"] == "task_progress"
        assert result[0]["title"] == "Pipeline Status"
        assert result[0]["tasks"] == tasks


# ── Chaining and terminal methods ───────────────────────────────


class TestChaining:
    def test_chaining_multiple_components(self):
        result = (
            ComponentBuilder()
            .header("Portfolio Summary")
            .divider()
            .section("Your holdings.", title="Overview")
            .stats([{"label": "SOL", "value": "$142.50"}])
            .actions([{"text": "Refresh", "value": "refresh"}])
            .build()
        )
        assert len(result) == 5
        assert result[0]["type"] == "header"
        assert result[1]["type"] == "divider"
        assert result[2]["type"] == "section"
        assert result[3]["type"] == "stats"
        assert result[4]["type"] == "actions"

    def test_build_returns_list_of_dicts(self):
        result = ComponentBuilder().header("H").divider().build()
        assert isinstance(result, list)
        for item in result:
            assert isinstance(item, dict)

    def test_build_returns_copy(self):
        """Modifying the returned list should not affect the builder."""
        builder = ComponentBuilder().header("H")
        result1 = builder.build()
        result2 = builder.build()
        assert result1 == result2
        result1.append({"type": "extra"})
        assert len(builder.build()) == 1

    def test_build_single_returns_dict(self):
        comp = ComponentBuilder().header("H").build_single()
        assert isinstance(comp, dict)
        assert comp["type"] == "header"

    def test_build_single_empty_raises(self):
        with pytest.raises(ValueError, match="no components"):
            ComponentBuilder().build_single()

    def test_build_single_returns_first(self):
        """build_single returns only the first component."""
        comp = ComponentBuilder().header("First").divider().build_single()
        assert comp["type"] == "header"
        assert comp["text"] == "First"

    def test_empty_build(self):
        result = ComponentBuilder().build()
        assert result == []


class TestAllComponentTypes:
    """Verify that every builder method produces the correct 'type' field."""

    def test_all_type_fields(self):
        builder = ComponentBuilder()

        # Build one of each type
        builder.header("H")
        builder.section("S")
        builder.divider()
        builder.context([{"type": "text", "text": "ctx"}])
        builder.actions([{"text": "A", "value": "a"}])
        builder.image("https://example.com/img.jpg")
        builder.alert("info", "Alert")
        builder.progress(50.0)
        builder.stats([{"label": "L", "value": "V"}])
        builder.table([{"key": "k", "label": "L"}], [{"k": "v"}])
        builder.tabs([{"id": "t", "label": "T", "content": "C"}])
        builder.accordion([{"id": "a", "title": "A", "content": "C"}])
        builder.carousel([{"title": "I"}])
        builder.user_card("Name")
        builder.token_table([{"name": "N", "symbol": "S", "price": "P"}])
        builder.token_card("N", "S", "P")
        builder.price_chart([{"t": 0, "o": 0, "h": 0, "l": 0, "c": 0, "v": 0}])
        builder.wallet_address("addr")
        builder.leaderboard([{"rank": 1, "name": "N", "value": "V"}])
        builder.form("f1", [])
        builder.text_input("ti")
        builder.select_menu("sm", [{"text": "T", "value": "V"}])
        builder.checkbox("cb", "Label")
        builder.textarea("ta")
        builder.live_price_ticker("SOL", 100.0, 1.0)
        builder.task_progress("Title", [{"id": "t", "label": "L", "status": "pending"}])

        result = builder.build()
        expected_types = [
            "header", "section", "divider", "context", "actions",
            "image", "alert", "progress", "stats", "table",
            "tabs", "accordion", "carousel", "user_card", "token_table",
            "token_card", "price_chart", "wallet_address", "leaderboard",
            "form", "text_input", "select_menu", "checkbox", "textarea",
            "live_price_ticker", "task_progress",
        ]
        actual_types = [c["type"] for c in result]
        assert actual_types == expected_types
