"""
Test: Dynamic Component Creation via ModuleWrapper and Qdrant Relationships

This test file demonstrates how to:
1. Query Qdrant for component relationships
2. Load components via ModuleWrapper
3. Dynamically instantiate and render complex widgets
4. Build cards from relationship data

KEY CONCEPTS:
=============

1. COMPONENT LOADING via ModuleWrapper:
   - Components are loaded by path: wrapper.get_component_by_path("card_framework.v2.widgets.Grid")
   - ComponentRegistry provides a cache and fallback path resolution

2. RELATIONSHIP DATA in Qdrant:
   - Each component has indexed relationships (child_classes, json_path, etc.)
   - Example: Grid has children [OnClick, BorderStyle]
   - This tells us Grid can have on_click and border_style attributes

3. INSTANTIATION PATTERNS:
   - Nested components: Grid(items=[GridItem(...), GridItem(...)])
   - Action components: OnClick(open_link=OpenLink(url="..."))
   - Enums are often nested: SelectionInput.SelectionType.CHECK_BOX

4. RENDERING:
   - All components have .render() method
   - Returns Google Chat JSON format (snake_case internally, camelCase for API)

5. API LIMITATIONS (verified via curl):
   - GridItem does NOT support onClick (whole Grid does)
   - Image widget supports onClick
   - ChipList/Chip support onClick

6. CARD_FRAMEWORK vs GOOGLE CHAT API GAPS:
   The card_framework library does NOT cover all Google Chat API features:

   SelectionType enum has: SWITCH, CHECK_BOX, RADIO_BUTTON, DROPDOWN
   API also supports: MULTI_SELECT (with platformDataSource, externalDataSource)

   Missing/incomplete widgets in card_framework:
   - Columns layout (columnItems with horizontalSizeStyle, verticalAlignment)
   - DateTimePicker (DATE_AND_TIME, DATE_ONLY, TIME_ONLY)
   - TextInput autoCompleteAction & initialSuggestions
   - Material icon params: fill, weight, grade, opticalSize
   - Grid borderStyle with strokeColor RGBA and cornerRadius
   - Grid item cropStyle (CIRCLE, SQUARE, RECTANGLE_4_3, RECTANGLE_CUSTOM)
   - Button types: FILLED, OUTLINED, FILLED_TONAL with custom color RGBA
   - OverflowMenu with nested items
   - fixedFooter with primaryButton/secondaryButton
   - cardActions array
   - peekCardHeader
   - DecoratedText switchControl (inline switch/checkbox)

   For advanced features, use raw JSON dicts passed to sections parameter.

Usage:
    pytest tests/test_dynamic_component_creation.py -v -s
"""

import json
import os
from typing import Any, Dict, List, Optional, Type

import pytest
from qdrant_client import models

from adapters.module_wrapper import ModuleWrapper
from config.qdrant_client import get_qdrant_client
from config.settings import settings


@pytest.fixture(scope="module")
def wrapper():
    """Get ModuleWrapper instance for loading components."""
    return ModuleWrapper(
        module_or_name="card_framework",
        qdrant_url=os.getenv("QDRANT_URL"),
        qdrant_api_key=os.getenv("QDRANT_KEY"),
        collection_name=settings.card_collection,
        auto_initialize=False,
    )


@pytest.fixture(scope="module")
def qdrant_client():
    """Get Qdrant client for querying relationships."""
    return get_qdrant_client()


class ComponentRegistry:
    """
    Registry for loading and caching components from ModuleWrapper.

    Uses Qdrant relationship data to understand component structure.
    """

    # Known working paths for common components
    # NOTE: Some enums (like SelectionType) are NESTED within their parent class
    #       and cannot be loaded separately. Access via: SelectionInput.SelectionType
    KNOWN_PATHS = {
        # Core widgets
        "Grid": "card_framework.v2.widgets.Grid",
        "GridItem": "card_framework.v2.widgets.grid.GridItem",
        "ImageComponent": "card_framework.v2.widgets.grid.ImageComponent",
        "Image": "card_framework.v2.widgets.Image",
        "ChipList": "card_framework.v2.widgets.ChipList",
        "Chip": "card_framework.v2.widgets.chip_list.Chip",
        "SelectionInput": "card_framework.v2.widgets.SelectionInput",
        "SelectionItem": "card_framework.v2.widgets.selection_input.SelectionItem",
        # SelectionType is NESTED: use SelectionInput.SelectionType.MULTI_SELECT
        "TextInput": "card_framework.v2.widgets.TextInput",
        "ButtonList": "card_framework.v2.widgets.ButtonList",
        "Button": "card_framework.v2.widgets.button.Button",
        "DecoratedText": "card_framework.v2.widgets.DecoratedText",
        "TextParagraph": "card_framework.v2.widgets.TextParagraph",
        # Action components
        "OnClick": "card_framework.v2.widgets.on_click.OnClick",
        "OpenLink": "card_framework.v2.widgets.on_click.OpenLink",
        "Action": "card_framework.v2.widgets.on_click.Action",
        # Icon components
        "Icon": "card_framework.v2.widgets.icon.Icon",
        # Card structure
        "Card": "card_framework.v2.card.Card",
        "CardHeader": "card_framework.v2.card.CardHeader",
        "Section": "card_framework.v2.section.Section",
    }

    def __init__(self, wrapper: ModuleWrapper):
        self.wrapper = wrapper
        self._cache: Dict[str, Type] = {}

    def get(self, name: str) -> Optional[Type]:
        """Get a component class by name."""
        if name in self._cache:
            return self._cache[name]

        # Try known path first
        if name in self.KNOWN_PATHS:
            component = self.wrapper.get_component_by_path(self.KNOWN_PATHS[name])
            if component:
                self._cache[name] = component
                return component

        # Try common path patterns
        patterns = [
            f"card_framework.v2.widgets.{name}",
            f"card_framework.v2.widgets.{name.lower()}.{name}",
            f"card_framework.{name}",
        ]

        for path in patterns:
            try:
                component = self.wrapper.get_component_by_path(path)
                if component:
                    self._cache[name] = component
                    return component
            except Exception:
                continue

        return None


def get_component_relationships(client, component_name: str) -> Dict[str, Any]:
    """Query Qdrant for a component's relationship data."""
    results, _ = client.scroll(
        collection_name=settings.card_collection,
        scroll_filter=models.Filter(
            must=[
                models.FieldCondition(
                    key="type", match=models.MatchValue(value="class")
                ),
                models.FieldCondition(
                    key="name", match=models.MatchValue(value=component_name)
                ),
            ]
        ),
        limit=1,
        with_payload=True,
    )

    if results:
        return results[0].payload.get("relationships", {})
    return {}


class TestComponentLoading:
    """Test that we can load components via ModuleWrapper."""

    def test_load_grid_components(self, wrapper):
        """Test loading Grid and related components."""
        registry = ComponentRegistry(wrapper)

        Grid = registry.get("Grid")
        GridItem = registry.get("GridItem")
        ImageComponent = registry.get("ImageComponent")

        assert Grid is not None, "Grid should be loadable"
        assert GridItem is not None, "GridItem should be loadable"
        assert ImageComponent is not None, "ImageComponent should be loadable"

    def test_load_chip_components(self, wrapper):
        """Test loading ChipList and Chip components."""
        registry = ComponentRegistry(wrapper)

        ChipList = registry.get("ChipList")
        Chip = registry.get("Chip")

        assert ChipList is not None, "ChipList should be loadable"
        assert Chip is not None, "Chip should be loadable"

    def test_load_selection_components(self, wrapper):
        """Test loading SelectionInput components.

        NOTE: SelectionType is a NESTED ENUM within SelectionInput, not a separate class.
        Access pattern: SelectionInput.SelectionType.MULTI_SELECT
        """
        registry = ComponentRegistry(wrapper)

        SelectionInput = registry.get("SelectionInput")
        SelectionItem = registry.get("SelectionItem")

        assert SelectionInput is not None, "SelectionInput should be loadable"
        assert SelectionItem is not None, "SelectionItem should be loadable"

        # SelectionType is a nested enum - access via SelectionInput
        assert hasattr(SelectionInput, "SelectionType"), (
            "SelectionInput should have SelectionType enum"
        )
        SelectionType = SelectionInput.SelectionType

        # Available types: SWITCH, CHECK_BOX, RADIO_BUTTON, DROPDOWN
        assert hasattr(SelectionType, "CHECK_BOX"), (
            "SelectionType should have CHECK_BOX"
        )
        assert hasattr(SelectionType, "DROPDOWN"), "SelectionType should have DROPDOWN"
        print(f"\n✅ SelectionType enum values: {[m.name for m in SelectionType]}")

    def test_load_action_components(self, wrapper):
        """Test loading OnClick and OpenLink."""
        registry = ComponentRegistry(wrapper)

        OnClick = registry.get("OnClick")
        OpenLink = registry.get("OpenLink")

        assert OnClick is not None, "OnClick should be loadable"
        assert OpenLink is not None, "OpenLink should be loadable"


class TestComponentRendering:
    """Test that we can instantiate and render components."""

    def test_render_grid(self, wrapper):
        """Test rendering a Grid with items."""
        registry = ComponentRegistry(wrapper)

        Grid = registry.get("Grid")
        GridItem = registry.get("GridItem")
        ImageComponent = registry.get("ImageComponent")

        items = [
            GridItem(
                title="Item 1",
                image=ImageComponent(image_uri="https://example.com/1.png"),
            ),
            GridItem(
                title="Item 2",
                image=ImageComponent(image_uri="https://example.com/2.png"),
            ),
        ]

        grid = Grid(title="Test Grid", column_count=2, items=items)
        rendered = grid.render()

        assert "grid" in rendered
        assert rendered["grid"]["title"] == "Test Grid"
        assert len(rendered["grid"]["items"]) == 2
        print(f"\n✅ Grid rendered:\n{json.dumps(rendered, indent=2)}")

    def test_render_grid_with_onclick(self, wrapper):
        """Test rendering a Grid with onClick (verified to work in API)."""
        registry = ComponentRegistry(wrapper)

        Grid = registry.get("Grid")
        GridItem = registry.get("GridItem")
        ImageComponent = registry.get("ImageComponent")
        OnClick = registry.get("OnClick")
        OpenLink = registry.get("OpenLink")

        items = [
            GridItem(
                title="Item 1",
                image=ImageComponent(image_uri="https://example.com/1.png"),
            ),
            GridItem(
                title="Item 2",
                image=ImageComponent(image_uri="https://example.com/2.png"),
            ),
        ]

        # Grid-level onClick (NOT per-item - API doesn't support that)
        on_click = OnClick(open_link=OpenLink(url="https://developers.google.com/chat"))
        grid = Grid(
            title="Clickable Grid", column_count=2, items=items, on_click=on_click
        )

        rendered = grid.render()
        assert "onClick" in rendered["grid"] or "on_click" in rendered["grid"]
        print(f"\n✅ Grid with onClick rendered:\n{json.dumps(rendered, indent=2)}")

    def test_render_chiplist(self, wrapper):
        """Test rendering a ChipList with chips."""
        registry = ComponentRegistry(wrapper)

        ChipList = registry.get("ChipList")
        Chip = registry.get("Chip")
        OnClick = registry.get("OnClick")
        OpenLink = registry.get("OpenLink")

        if not all([ChipList, Chip, OnClick, OpenLink]):
            pytest.skip("Required components not available")

        chips = [
            Chip(
                label="Chip 1",
                on_click=OnClick(open_link=OpenLink(url="https://google.com")),
            ),
            Chip(
                label="Chip 2",
                on_click=OnClick(open_link=OpenLink(url="https://github.com")),
            ),
        ]

        chip_list = ChipList(chips=chips)
        rendered = chip_list.render()

        assert "chipList" in rendered or "chip_list" in rendered
        print(f"\n✅ ChipList rendered:\n{json.dumps(rendered, indent=2)}")

    def test_render_selection_input(self, wrapper):
        """Test rendering a SelectionInput with items.

        IMPORTANT: SelectionType is a nested enum within SelectionInput.
        Access pattern: SelectionInput.SelectionType.CHECK_BOX

        card_framework enum has: SWITCH, CHECK_BOX, RADIO_BUTTON, DROPDOWN

        NOTE: Google Chat API also supports MULTI_SELECT (with platformDataSource,
        externalDataSource, multiSelectMaxSelectedItems, multiSelectMinQueryLength)
        but this is NOT in the card_framework enum. Use raw string "MULTI_SELECT" if needed.
        """
        registry = ComponentRegistry(wrapper)

        SelectionInput = registry.get("SelectionInput")
        SelectionItem = registry.get("SelectionItem")

        if not all([SelectionInput, SelectionItem]):
            pytest.skip("Required components not available")

        # SelectionType is nested - access via SelectionInput class
        if not hasattr(SelectionInput, "SelectionType"):
            pytest.skip("SelectionInput doesn't have SelectionType enum")

        SelectionType = SelectionInput.SelectionType

        items = [
            SelectionItem(text="Option 1", value="opt1", bottom_text="Description 1"),
            SelectionItem(text="Option 2", value="opt2", bottom_text="Description 2"),
        ]

        # Use nested enum for type (CHECK_BOX allows multiple selections)
        selection = SelectionInput(
            name="test_selection",
            label="Select an option",
            type=SelectionType.CHECK_BOX,
            items=items,
        )

        rendered = selection.render()
        assert "selectionInput" in rendered or "selection_input" in rendered
        print(f"\n✅ SelectionInput rendered:\n{json.dumps(rendered, indent=2)}")


class TestRelationshipQueries:
    """Test querying and using relationship data from Qdrant."""

    def test_grid_relationships(self, qdrant_client):
        """Test that Grid has expected relationships indexed."""
        rels = get_component_relationships(qdrant_client, "Grid")

        assert "child_classes" in rels
        children = rels["child_classes"]

        # Grid should have OnClick and BorderStyle children
        assert "OnClick" in children, "Grid should have OnClick relationship"
        assert "BorderStyle" in children, "Grid should have BorderStyle relationship"

        print(f"\n✅ Grid relationships: {children}")

    def test_chip_relationships(self, qdrant_client):
        """Test that Chip has expected relationships indexed."""
        rels = get_component_relationships(qdrant_client, "Chip")

        assert "child_classes" in rels
        children = rels["child_classes"]

        # Chip should have Icon and OnClick children
        assert "Icon" in children, "Chip should have Icon relationship"
        assert "OnClick" in children, "Chip should have OnClick relationship"

        print(f"\n✅ Chip relationships: {children}")

    def test_selection_input_relationships(self, qdrant_client):
        """Test that SelectionInput has expected relationships indexed."""
        rels = get_component_relationships(qdrant_client, "SelectionInput")

        assert "child_classes" in rels
        children = rels["child_classes"]

        # SelectionInput should have SelectionType
        assert "SelectionType" in children, (
            "SelectionInput should have SelectionType relationship"
        )

        print(f"\n✅ SelectionInput relationships: {children}")


class TestDynamicCardBuilding:
    """Test building complete cards from component composition."""

    def test_build_complex_card(self, wrapper):
        """Test building a card with multiple widget types."""
        registry = ComponentRegistry(wrapper)

        # Load components
        Card = registry.get("Card")
        Section = registry.get("Section")
        Grid = registry.get("Grid")
        GridItem = registry.get("GridItem")
        ImageComponent = registry.get("ImageComponent")

        if not all([Card, Section, Grid, GridItem, ImageComponent]):
            pytest.skip("Required components not available")

        # Build grid
        grid_items = [
            GridItem(
                title="Item 1",
                image=ImageComponent(image_uri="https://example.com/1.png"),
            ),
            GridItem(
                title="Item 2",
                image=ImageComponent(image_uri="https://example.com/2.png"),
            ),
        ]
        grid = Grid(title="My Grid", column_count=2, items=grid_items)

        # Build section
        section = Section(header="Test Section", widgets=[grid])

        # Build card
        card = Card(sections=[section])
        rendered = card.render()

        # Card.render() returns {'card': {'sections': [...]}}
        assert "card" in rendered, "Card should render with 'card' key"
        assert "sections" in rendered["card"], "Card should have sections"
        print(f"\n✅ Complex card rendered:\n{json.dumps(rendered, indent=2)[:500]}...")

    def test_json_to_component_roundtrip(self, wrapper):
        """
        Test that we can take JSON structure, understand its components,
        and rebuild using our component classes.

        This validates that our relationship data is sufficient for reconstruction.
        """
        registry = ComponentRegistry(wrapper)

        # Original JSON structure (simplified)
        target_json = {
            "grid": {
                "title": "A fine collection",
                "columnCount": 2,
                "items": [
                    {
                        "title": "Item 1",
                        "image": {"imageUri": "https://example.com/1.png"},
                    },
                    {
                        "title": "Item 2",
                        "image": {"imageUri": "https://example.com/2.png"},
                    },
                ],
            }
        }

        # Rebuild using components
        Grid = registry.get("Grid")
        GridItem = registry.get("GridItem")
        ImageComponent = registry.get("ImageComponent")

        rebuilt_items = [
            GridItem(
                title=item["title"],
                image=ImageComponent(image_uri=item["image"]["imageUri"]),
            )
            for item in target_json["grid"]["items"]
        ]

        rebuilt_grid = Grid(
            title=target_json["grid"]["title"],
            column_count=target_json["grid"]["columnCount"],
            items=rebuilt_items,
        )

        rendered = rebuilt_grid.render()

        # Verify structure matches (key fields)
        assert rendered["grid"]["title"] == target_json["grid"]["title"]
        assert len(rendered["grid"]["items"]) == len(target_json["grid"]["items"])

        print("\n✅ JSON -> Component -> JSON roundtrip successful!")
        print(f"Original: {json.dumps(target_json, indent=2)[:200]}...")
        print(f"Rebuilt: {json.dumps(rendered, indent=2)[:200]}...")


if __name__ == "__main__":
    pytest.main([__file__, "-v", "-s"])
