"""Resolution modal for macro-to-value resolution.

Provides a modal screen that allows users to search and select
the target for a macro (fragment template or content part).
"""

import json
from typing import Any

from textual import on, work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.screen import ModalScreen
from textual.widgets import Button, Input, Label, OptionList, Static
from textual.widgets.option_list import Option

from alloy_runtime_sdk.api_client.client import ApiClient
from alloy_runtime_types.enums.content_enums import ContentStorageType
from alloy_runtime_types.enums.template_enums import TemplateContentType
from alloy_runtime_sdk.exceptions.errors import RequestError


class ResolutionModal(ModalScreen[dict[str, Any] | None]):
    """Modal for resolving a macro to a specific template or content part.

    For fragment macros: searches templates and returns the fragment name
    For text/json macros: searches content parts and returns the UUID

    Usage:
        def on_resolution_complete(result: dict | None):
            if result:
                # result["value"] is the resolved value (name or UUID)
                # result["display"] is a human-readable display string
                pass

        self.push_screen(
            ResolutionModal(
                client=server_client,
                macro_type="fragment",
                query="header",
            ),
            on_resolution_complete
        )
    """

    BINDINGS = [
        Binding("escape", "cancel", "Cancel", show=True),
        Binding("enter", "select", "Select", show=True),
    ]

    def __init__(
        self,
        client: ApiClient,
        macro_type: str,
        query: str,
        **kwargs: Any,
    ) -> None:
        """Initialize the resolution modal.

        Args:
            client: ApiClient instance for API calls
            macro_type: Type of macro being resolved (fragment, text, json, schema)
            query: Initial search query from the macro
        """
        super().__init__(**kwargs)
        self._client = client
        self._macro_type = macro_type
        self._initial_query = query
        self._search_results: list[dict[str, Any]] = []
        self._last_query: str = ""

    def compose(self) -> ComposeResult:
        type_label = {
            "fragment": "Fragment Template",
            "text": "Content Part (Text)",
            "json": "Content Part (JSON)",
            "schema": "Schema (JSON/Regex)",
        }.get(self._macro_type, self._macro_type)

        with Vertical():
            yield Label(f"Resolve @{self._macro_type}(...)", classes="modal-title")
            yield Static(f"Searching for: {type_label}", classes="macro-info")
            yield Input(
                value=self._initial_query,
                placeholder="Type to search...",
                id="search-input",
            )
            yield OptionList(id="results-list")
            yield Static("Type to search...", classes="status-line", id="status")
            with Vertical(classes="button-row"):
                yield Button("Select", id="select-btn", variant="success")
                yield Button("Skip", id="skip-btn", variant="warning")

    def on_mount(self) -> None:
        """Focus the search input and trigger initial search."""
        self.query_one("#search-input", Input).focus()
        # Trigger initial search with the query from the macro
        if self._initial_query:
            self._search(self._initial_query)

    @on(Input.Changed, "#search-input")
    def on_search_changed(self, event: Input.Changed) -> None:
        """Search on input change."""
        query = event.value.strip()
        if query != self._last_query:
            self._last_query = query
            self._search(query)

    @work(exclusive=True)
    async def _search(self, query: str) -> None:
        """Fetch results from the appropriate API endpoint."""
        status = self.query_one("#status", Static)
        options = self.query_one("#results-list", OptionList)

        if not query:
            status.update("Type to search...")
            options.clear_options()
            self._search_results = []
            return

        status.update("Searching...")

        try:
            if self._macro_type == "fragment":
                results = await self._search_templates(query)
            elif self._macro_type == "schema":
                results = await self._search_schemas(query)
            else:
                results = await self._search_content_parts(query)

            self._search_results = results
            self._update_options()

            count = len(results)
            if count == 0:
                status.update("No results found")
            else:
                status.update(f"{count} result{'s' if count != 1 else ''} found")

        except RequestError as e:
            status.update(f"[red]Error: {e}[/]")
            self._search_results = []
            options.clear_options()
        except Exception as e:
            status.update(f"[red]Error: {e}[/]")
            self._search_results = []
            options.clear_options()

    async def _search_templates(self, query: str) -> list[dict[str, Any]]:
        """Search for fragment templates."""
        response = await self._client.list_templates(
            search=query,
            content_type=TemplateContentType.FRAGMENT,
            limit=50,
        )
        return [template.model_dump() for template in response.templates]

    async def _search_content_parts(self, query: str) -> list[dict[str, Any]]:
        """Search for content parts filtered by storage type based on macro type."""
        # Determine storage type filter based on macro type
        # text needs content_text (storage_type=text)
        # json needs content_structured (storage_type=structured)
        storage_type = (
            ContentStorageType.TEXT
            if self._macro_type == "text"
            else ContentStorageType.STRUCTURED
        )

        response = await self._client.list_content_parts(
            search=query,
            storage_type=storage_type,
            limit=50,
        )
        return [part.model_dump() for part in response.content_parts]

    async def _search_schemas(self, query: str) -> list[dict[str, Any]]:
        """Search for schemas."""
        response = await self._client.list_schemas(search=query, limit=50)
        return [schema.model_dump() for schema in response.schemas]

    def _update_options(self) -> None:
        """Update the option list with current search results."""
        options = self.query_one("#results-list", OptionList)
        options.clear_options()

        for i, item in enumerate(self._search_results):
            display_text = self._format_result(item)
            options.add_option(Option(display_text, id=f"opt-{i}"))

        if self._search_results:
            options.highlighted = 0

    def _format_result(self, item: dict[str, Any]) -> str:
        """Format a search result for display."""
        if self._macro_type == "fragment":
            # Template result
            name = item.get("name", "Unknown")
            version = item.get("latest_version_number", "?")
            visibility = item.get("visibility_id", "organization")
            vis_label = {
                "private": "Private",
                "organization": "Organization",
                "public": "Public",
            }.get(visibility, visibility)
            return f"{name} (v{version}) [{vis_label}]"
        elif self._macro_type == "schema":
            # Schema result
            name = item.get("schema_name", "Unknown")
            version = item.get("version", "?")
            format_id = item.get("schema_format_id", "unknown")
            description = item.get("description") or ""
            desc_preview = f" - {description}" if description else ""
            return f"{name} (v{version}) [{format_id}]{desc_preview}"
        else:
            # Content part result
            content_type = item.get("content_type_id", "unknown")
            part_id = str(item.get("id", ""))

            # Get preview based on storage type
            if self._macro_type == "text":
                content_text = item.get("content_text") or ""
                preview = content_text
                preview = preview.replace("\n", " ")
            else:  # json
                content_structured = item.get("content_structured")
                if content_structured:
                    preview = json.dumps(content_structured)
                else:
                    preview = "(no structured content)"

            return f"[{content_type}] {preview} ({part_id})"

    def _get_selected_value(self) -> dict[str, Any] | None:
        """Get the resolved value for the currently selected item."""
        options = self.query_one("#results-list", OptionList)
        if options.highlighted is None:
            return None

        index = options.highlighted
        if index < 0 or index >= len(self._search_results):
            return None

        item = self._search_results[index]

        if self._macro_type == "fragment":
            # Return the fragment NAME (not UUID) - this is what the renderer expects
            return {
                "value": item.get("name"),
                "display": self._format_result(item),
                "item": item,
            }
        elif self._macro_type == "schema":
            # Return the schema NAME - renderer supports name lookups with priority ordering
            return {
                "value": item.get("schema_name"),
                "display": self._format_result(item),
                "item": item,
            }
        else:
            # Return the content part UUID
            return {
                "value": str(item.get("id")),
                "display": self._format_result(item),
                "item": item,
            }

    @on(OptionList.OptionSelected, "#results-list")
    def on_option_selected(self, event: OptionList.OptionSelected) -> None:
        """Handle double-click or enter on option."""
        self.action_select()

    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Handle button presses."""
        if event.button.id == "select-btn":
            self.action_select()
        elif event.button.id == "skip-btn":
            self.action_cancel()

    def action_select(self) -> None:
        """Select the current item and dismiss."""
        result = self._get_selected_value()
        if result:
            self.dismiss(result)
        else:
            # No selection - show feedback
            status = self.query_one("#status", Static)
            status.update("[yellow]Select an item first[/]")

    def action_cancel(self) -> None:
        """Cancel resolution and dismiss with None."""
        self.dismiss(None)
