"""Content screen for browsing and managing content parts.

This screen provides:
- Search and browse AI-generated content parts
- View content details in preview panel
- Manage tags on content
- Copy content text/JSON
- Open content in external editor
"""

import json
import os
import subprocess
import tempfile
from typing import TYPE_CHECKING, Any
from uuid import UUID

from textual import on, work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.widgets import DataTable, Input, Static

from alloy_runtime_types.dtos.content import UpdateContentPartRequest
from alloy_runtime_types.enums.content_enums import ContentSortField

from cli.infrastructure.forms.tag_management_modal import TagManagementModal
from cli.infrastructure.tui.clipboard import copy_to_clipboard
from cli.tui.widgets.confirm_modal import ConfirmModal
from cli.infrastructure.tui.formatters import (
    format_datetime,
    format_tags_list,
)
from cli.tui.screens.base import BrowserWidget
from cli.tui.screens.nav_screen import NavScreen
from cli.tui.screens.models import ContentPartView

if TYPE_CHECKING:
    from cli.tui.app import AlloyRuntimeApp


class ContentWidget(BrowserWidget[ContentPartView]):
    """Widget for browsing and managing content parts."""

    app: "AlloyRuntimeApp"

    TITLE = "Content"
    TABLE_COLUMNS = ["Role", "Type", "Content", "Tags", "Model", "Updated"]

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the content widget."""
        super().__init__(**kwargs)
        self._pending_tag_update_content_id: str | None = None
        # Additional filter state for three-input search
        self._last_tag_filter: str = ""
        self._last_external_id: str = ""

    def compose(self) -> ComposeResult:
        """Compose the browser layout with three search inputs."""
        with Vertical(id="browser-root"):
            with Vertical(id="search-container"):
                with Horizontal(id="search-row"):
                    yield Input(
                        placeholder="Search content...",
                        id="search-input",
                    )
                    yield Input(
                        placeholder="Tag filter (e.g. code.*)",
                        id="tag-input",
                    )
                    yield Input(
                        placeholder="External ID",
                        id="external-id-input",
                    )
                yield Static("Loading...", id="status-bar")

            with Horizontal(id="main-container"):
                with Vertical(id="list-container"):
                    yield DataTable(id="data-table", cursor_type="row")

                with Vertical(id="detail-container"):
                    yield Static("Preview", id="preview-title")
                    with VerticalScroll(id="preview-scroll"):
                        yield Static(
                            "Select an item to view details",
                            id="preview-content",
                        )

    @on(Input.Changed, "#tag-input")
    def _on_tag_input_changed(self, event: Input.Changed) -> None:
        """Handle tag filter input changes."""
        tag_filter = event.value.strip()
        if tag_filter != self._last_tag_filter:
            self._last_tag_filter = tag_filter
            self._trigger_search()

    @on(Input.Changed, "#external-id-input")
    def _on_external_id_input_changed(self, event: Input.Changed) -> None:
        """Handle external ID input changes."""
        external_id = event.value.strip()
        if external_id != self._last_external_id:
            self._last_external_id = external_id
            self._trigger_search()

    def _trigger_search(self) -> None:
        """Trigger a search with all current filter values."""
        self._do_search(self._last_search)

    async def _fetch_items(self, query: str) -> tuple[list[ContentPartView], int]:
        """Fetch content parts from the API with all filters."""
        client = await self._ensure_client()
        response = await client.list_content_parts(
            search=query if query else None,
            tag_path=self._last_tag_filter if self._last_tag_filter else None,
            external_id=self._last_external_id if self._last_external_id else None,
            sort_by=ContentSortField.UPDATED_AT,
            limit=50,
        )

        items = [ContentPartView.from_response(part) for part in response.content_parts]
        return items, response.total_count

    def _format_row(self, item: ContentPartView) -> tuple[str, ...]:
        """Format a content part as a table row."""
        tags_dicts = [{"tag_path": t.tag_path} for t in item.tags]
        return (
            item.message_role,
            item.content_type_id,
            self._format_content_preview(item),
            format_tags_list(tags_dicts, max_display=2),
            self._format_model_info(item),
            format_datetime(item.updated_at),
        )

    def _format_content_preview(self, item: ContentPartView) -> str:
        """Format content for table display."""
        if item.content_text:
            text = item.content_text.replace("\n", " ").strip()
            return text

        if item.content_structured:
            try:
                json_str = json.dumps(item.content_structured, ensure_ascii=False)
                return json_str
            except (TypeError, ValueError):
                return str(item.content_structured)

        return "-"

    def _format_model_info(self, item: ContentPartView) -> str:
        """Format model info for table display."""
        if not item.execution_metadata:
            return "-"

        provider = item.execution_metadata.provider_key
        model = item.execution_metadata.provider_model_name

        if provider and model:
            return f"{provider}/{model}"
        elif model:
            return model
        elif provider:
            return provider
        return "-"

    def _update_preview(self, item: ContentPartView) -> None:
        """Update the preview pane with content details."""
        preview = self.query_one("#preview-content", Static)

        storage = "structured" if item.content_structured else "text"
        created_at = format_datetime(item.created_at)

        if item.content_structured:
            try:
                content_display = json.dumps(item.content_structured, indent=2)
            except (TypeError, ValueError):
                content_display = str(item.content_structured)
        elif item.content_text:
            content_display = item.content_text
        else:
            content_display = "(empty)"

        tags_display = ", ".join(t.tag_path for t in item.tags) if item.tags else "-"

        if item.execution_metadata:
            model_info = (
                f"{item.execution_metadata.provider_key}/"
                f"{item.execution_metadata.provider_model_name}"
            )
        else:
            model_info = "-"

        # Display external_id prominently if present
        external_id_display = item.external_id or "-"

        preview_text = f"""[bold]External ID:[/] {external_id_display}
 [bold]ID:[/] {item.id}
 [bold]Role:[/] {item.message_role}
 [bold]Type:[/] {item.content_type_id}
 [bold]Storage:[/] {storage}
 [bold]Model:[/] {model_info}
 [bold]Tags:[/] {tags_display}
 [bold]Created:[/] {created_at}
 
 [bold]Content:[/]
 {content_display}"""

        preview.update(preview_text)

    # =========================================================================
    # Public methods for parent Screen to call
    # =========================================================================

    def copy_content_id(self) -> None:
        """Copy the selected content ID to clipboard.

        Prefers external_id if available, otherwise falls back to the internal UUID.
        """
        part = self.get_selected_item()
        if not part:
            self.app.notify("No content selected", severity="warning")
            return

        # Prefer external_id, fallback to internal id
        if part.external_id:
            copy_to_clipboard(self.app, part.external_id, "External ID")
        else:
            copy_to_clipboard(self.app, str(part.id), "ID")

    def copy_content(self) -> None:
        """Copy the selected content to clipboard."""
        part = self.get_selected_item()
        if not part:
            self.app.notify("No content to copy", severity="warning")
            return

        if part.content_structured:
            try:
                content = json.dumps(
                    part.content_structured, indent=2, ensure_ascii=False
                )
            except (TypeError, ValueError):
                content = str(part.content_structured)
        elif part.content_text:
            content = part.content_text
        else:
            self.app.notify("Content is empty", severity="warning")
            return

        preview_str = content.replace("\n", " ")
        copy_to_clipboard(self.app, content, f"content: {preview_str}")

    def manage_tags(self) -> None:
        """Open tag management modal for selected content."""
        part = self.get_selected_item()
        if not part:
            self.app.notify("No content to tag", severity="warning")
            return

        self._pending_tag_update_content_id = part.id

        # Convert tags to dict format expected by modal
        current_tags = [{"tag_path": t.tag_path} for t in part.tags]
        self._show_tag_management_modal(str(part.id), current_tags)

    def delete_content(self) -> None:
        """Delete the currently selected content part."""
        part = self.get_selected_item()
        if not part:
            self.app.notify("No content selected", severity="warning")
            return

        # Create a preview of the content for the confirmation message
        if part.content_text:
            preview = part.content_text.replace("\n", " ")
        elif part.content_structured:
            preview = f"[{part.content_type_id}]"
        else:
            preview = "(empty)"

        self.app.push_screen(
            ConfirmModal(
                title="Delete Content",
                message=f"Delete content '{preview}'? This cannot be undone.",
            ),
            lambda confirmed: self._on_delete_confirmed(confirmed, part.id),
        )

    def _on_delete_confirmed(self, confirmed: bool | None, content_id: str) -> None:
        """Callback when delete confirmation modal is dismissed."""
        if confirmed:
            self._execute_delete(content_id)

    @work(exclusive=True)
    async def _execute_delete(self, content_id: str) -> None:
        """Execute the delete operation."""
        try:
            client = await self._ensure_client()
            response = await client.delete_content_part(UUID(content_id))

            # Build notification message with cascade info
            messages = ["Content deleted"]
            if response.deleted_message_id:
                messages.append("orphaned message removed")
            if response.deleted_session_id:
                messages.append("orphaned session removed")

            self.app.notify(
                ", ".join(messages),
                severity="information",
            )
            # Refresh the content list
            self._do_search(self._last_search)

        except Exception as e:
            self.app.notify(f"Failed to delete content: {e}", severity="error")

    def open_in_editor(self) -> None:
        """Open content in external editor (read-only view)."""
        part = self.get_selected_item()
        if not part:
            self.app.notify("No content to open", severity="warning")
            return

        if part.content_structured:
            try:
                content = json.dumps(
                    part.content_structured, indent=2, ensure_ascii=False
                )
                suffix = ".json"
            except (TypeError, ValueError):
                content = str(part.content_structured)
                suffix = ".txt"
        elif part.content_text:
            content = part.content_text
            suffix = ".txt"
        else:
            self.app.notify("Content is empty", severity="warning")
            return

        editor = os.environ.get("EDITOR") or os.environ.get("VISUAL") or "vi"

        try:
            with tempfile.NamedTemporaryFile(
                mode="w",
                suffix=suffix,
                prefix="content_",
                delete=False,
                encoding="utf-8",
            ) as f:
                f.write(content)
                temp_path = f.name

            with self.app.suspend():
                subprocess.run([editor, temp_path], check=False)

            try:
                os.unlink(temp_path)
            except OSError:
                pass

            self.app.notify("Editor closed", severity="information")

        except Exception as e:
            self.app.notify(f"Failed to open editor: {e}", severity="error")

    # =========================================================================
    # Internal async operations
    # =========================================================================

    @work(exclusive=True)
    async def _show_tag_management_modal(
        self, content_part_id: str, current_tags: list[dict[str, Any]]
    ) -> None:
        """Show the tag management modal (async to get client)."""
        client = await self._ensure_client()
        modal = TagManagementModal(
            client=client,
            content_part_id=content_part_id,
            current_tags=current_tags,
        )
        self.app.push_screen(modal, self._on_tags_updated)

    def _on_tags_updated(self, result: list[str] | None) -> None:
        """Handle tag update modal result."""
        if result is not None:
            self._save_tags(result)

    @work(exclusive=True)
    async def _save_tags(self, tag_paths: list[str]) -> None:
        """Save updated tags to the server."""
        if not self._pending_tag_update_content_id:
            self.app.notify("No content selected for tag update", severity="error")
            return

        content_id = UUID(self._pending_tag_update_content_id)

        try:
            client = await self._ensure_client()
            request = UpdateContentPartRequest(
                content_text=None, content_structured=None, tag_paths=tag_paths
            )
            await client.update_content_part(content_id, request)

            self.app.notify("Tags updated successfully", severity="information")
            self._do_search(self._last_search)

        except Exception as e:
            self.app.notify(f"Failed to update tags: {e}", severity="error")
        finally:
            self._pending_tag_update_content_id = None


class ContentScreen(NavScreen):
    """Screen for browsing and managing content parts.

    This is a proper Textual Screen that wraps the ContentWidget
    and provides keybindings that work regardless of focus.
    """

    app: "AlloyRuntimeApp"

    SCREEN_ID = "content"

    DEFAULT_CSS = """
    ContentScreen #screen-root {
        height: 1fr;
    }
    
    ContentScreen .browser-widget {
        height: 100%;
        width: 100%;
    }
    """

    # Screen-specific bindings (Ctrl+ prefixed)
    BINDINGS = NavScreen.BINDINGS + [
        Binding("ctrl+f", "focus_search", "Search", show=True),
        Binding("ctrl+r", "refresh", "Refresh", show=True),
        Binding("ctrl+y", "copy_content", "Copy Content", show=True),
        Binding("ctrl+i", "copy_id", "Copy ID", show=True),
        Binding("ctrl+t", "manage_tags", "Tags", show=True),
        Binding("ctrl+e", "open_in_editor", "View", show=True),
        Binding("ctrl+d", "delete_content", "Delete", show=True),
        Binding("escape", "focus_table", "Focus List", show=False),
        Binding("enter", "copy_content", "Copy Content", show=False),
    ]

    def compose_content(self) -> ComposeResult:
        """Compose the screen content."""
        yield ContentWidget(id="content-widget")

    def on_screen_resume(self) -> None:
        """Focus the data table when screen becomes active."""
        self.call_after_refresh(self._focus_table)

    def _focus_table(self) -> None:
        """Focus the data table."""
        try:
            widget = self.query_one("#content-widget", ContentWidget)
            widget.focus_table()
        except Exception:
            pass

    def _get_widget(self) -> ContentWidget:
        """Get the content widget."""
        return self.query_one("#content-widget", ContentWidget)

    # =========================================================================
    # Actions (keybinding handlers)
    # =========================================================================

    def action_focus_search(self) -> None:
        """Focus the search input."""
        self._get_widget().focus_search()

    def action_focus_table(self) -> None:
        """Focus the data table."""
        self._get_widget().focus_table()

    def action_refresh(self) -> None:
        """Refresh the current search results."""
        self._get_widget().refresh_data()

    def action_copy_id(self) -> None:
        """Copy the content ID to clipboard."""
        self._get_widget().copy_content_id()

    def action_copy_content(self) -> None:
        """Copy the content to clipboard."""
        self._get_widget().copy_content()

    def action_manage_tags(self) -> None:
        """Open tag management modal."""
        self._get_widget().manage_tags()

    def action_open_in_editor(self) -> None:
        """Open content in external editor."""
        self._get_widget().open_in_editor()

    def action_delete_content(self) -> None:
        """Delete the selected content."""
        self._get_widget().delete_content()
