"""Injection service for TUI chat.

Wraps the shared InjectionResolver and provides caching for autocomplete.
This service is designed for use with Textual's async workers.

Supports the same macro syntax as templates:
- @fragment(name)       - Include a fragment template
- @text(name)           - Insert text from a content part
- @json(name)           - Insert JSON from a content part
- @schema(name)         - Reference a schema definition
"""

import logging
import time
from dataclasses import dataclass

from alloy_runtime_sdk.api_client.client import ApiClient

from cli.infrastructure.injection.resolver import (
    InjectionResolver,
    ResolvedMessage,
)

logger = logging.getLogger(__name__)

# Cache TTL in seconds (5 minutes)
CACHE_TTL = 300


@dataclass
class FragmentInfo:
    """Fragment template information for autocomplete."""

    id: str
    name: str
    description: str | None
    content_type_id: str


@dataclass
class ContentPartInfo:
    """Content part information for autocomplete."""

    id: str
    content_type_id: str
    content_text: str | None
    has_structured: bool


@dataclass
class SchemaInfo:
    """Schema information for autocomplete."""

    id: str
    schema_name: str
    version: str
    schema_format_id: str
    description: str | None


@dataclass
class InjectionCompletion:
    """A single injection autocomplete suggestion."""

    text: str  # Full insertion text: @fragment(my-fragment)
    display_name: str  # Display name: "my-fragment"
    description: str  # Description/type info
    injection_type: str  # "fragment", "text", "json", or "schema"


class InjectionService:
    """Manages injection resolution and caching for TUI.

    Features:
    - Wraps InjectionResolver for API calls
    - Caches fragments/content/schemas for autocomplete
    - Provides async methods suitable for Textual workers
    - Shares ApiClient with the rest of the TUI

    Usage:
        service = InjectionService(client)
        await service.refresh_cache()

        # Check for completions
        completions = service.get_completions("fragment", "my-fra")

        # Resolve message before sending
        if has_injections(message):
            result = await service.resolve_message(message)
            if result.has_errors:
                # Handle errors
                ...
            message = result.text
    """

    def __init__(self, client: ApiClient):
        """Initialize the injection service.

        Args:
            client: Shared ApiClient instance from the TUI app.
        """
        self._client = client
        self._resolver = InjectionResolver(client=client)

        # Autocomplete caches
        self._fragment_cache: list[FragmentInfo] = []
        self._content_cache: list[ContentPartInfo] = []
        self._schema_cache: list[SchemaInfo] = []
        self._cache_expiry: float = 0

    @property
    def is_cache_valid(self) -> bool:
        """Check if the autocomplete cache is still valid."""
        return time.time() < self._cache_expiry

    async def resolve_message(self, message: str) -> ResolvedMessage:
        """Resolve all injection patterns in a message.

        Args:
            message: User message with potential injection patterns.

        Returns:
            ResolvedMessage with resolved text and metadata.
        """
        return await self._resolver.resolve(message)

    async def refresh_cache(self) -> None:
        """Refresh all autocomplete caches from the API.

        This should be called:
        - On session load/create
        - Periodically in the background
        - When cache expires and user triggers autocomplete
        """
        try:
            # Fetch all data in parallel
            import asyncio

            results = await asyncio.gather(
                self._fetch_fragments(),
                self._fetch_content(),
                self._fetch_schemas(),
                return_exceptions=True,
            )

            # Log any errors but don't fail
            for i, result in enumerate(results):
                if isinstance(result, Exception):
                    logger.debug(
                        "injection_cache_refresh_partial_failure",
                        extra={"index": i, "error": str(result)},
                    )

            # Update expiry
            self._cache_expiry = time.time() + CACHE_TTL

            logger.debug(
                "injection_cache_refreshed",
                extra={
                    "fragments": len(self._fragment_cache),
                    "content": len(self._content_cache),
                    "schemas": len(self._schema_cache),
                },
            )

        except Exception as e:
            logger.debug(
                "injection_cache_refresh_failed",
                extra={"error": str(e)},
            )

    async def _fetch_fragments(self) -> None:
        """Fetch fragment templates for autocomplete cache.

        Only fetches templates with content_type='fragment'.
        """
        from alloy_runtime_types.enums.template_enums import TemplateContentType

        try:
            response = await self._client.list_templates(
                limit=100, content_type=TemplateContentType.FRAGMENT
            )
            self._fragment_cache = [
                FragmentInfo(
                    id=str(t.id),
                    name=t.name,
                    description=t.description,
                    content_type_id=t.content_type_id,
                )
                for t in response.templates
            ]
        except Exception as e:
            logger.debug("Failed to fetch fragments: %s", e)
            raise

    async def _fetch_content(self) -> None:
        """Fetch content parts for autocomplete cache."""
        try:
            response = await self._client.list_content_parts(limit=100)
            self._content_cache = [
                ContentPartInfo(
                    id=str(p.id),
                    content_type_id=p.content_type_id,
                    content_text=p.content_text,
                    has_structured=p.content_structured is not None,
                )
                for p in response.content_parts
            ]
        except Exception as e:
            logger.debug("Failed to fetch content parts: %s", e)
            raise

    async def _fetch_schemas(self) -> None:
        """Fetch schemas for autocomplete cache."""
        try:
            response = await self._client.list_schemas(limit=100)
            self._schema_cache = [
                SchemaInfo(
                    id=str(s.id),
                    schema_name=s.schema_name,
                    version=str(s.version),
                    schema_format_id=s.schema_format_id,
                    description=s.description,
                )
                for s in response.schemas
            ]
        except Exception as e:
            logger.debug("Failed to fetch schemas: %s", e)
            raise

    def get_completions(
        self,
        injection_type: str,
        partial_identifier: str = "",
        limit: int = 10,
    ) -> list[InjectionCompletion]:
        """Get autocomplete suggestions for a partial injection.

        Args:
            injection_type: Type of injection ("fragment", "text", "json", "schema").
            partial_identifier: Partial identifier typed by user.
            limit: Maximum number of suggestions to return.

        Returns:
            List of InjectionCompletion objects.
        """
        partial_lower = partial_identifier.lower()

        if injection_type == "fragment":
            return self._get_fragment_completions(partial_lower, limit)
        elif injection_type in ("text", "json"):
            return self._get_content_completions(
                partial_lower, injection_type == "json", limit
            )
        elif injection_type == "schema":
            return self._get_schema_completions(partial_lower, limit)
        else:
            return []

    def _get_fragment_completions(
        self, partial: str, limit: int
    ) -> list[InjectionCompletion]:
        """Get fragment completions matching the partial identifier."""
        completions: list[InjectionCompletion] = []

        for fragment in self._fragment_cache:
            name_lower = fragment.name.lower()
            desc_lower = (fragment.description or "").lower()

            if partial in name_lower or partial in desc_lower:
                # Build the insertion text (no quotes, no variables)
                text = f"@fragment({fragment.name})"

                # Format description
                description = fragment.description or "no description"

                completions.append(
                    InjectionCompletion(
                        text=text,
                        display_name=fragment.name,
                        description=description,
                        injection_type="fragment",
                    )
                )

                if len(completions) >= limit:
                    break

        return completions

    def _get_content_completions(
        self, partial: str, is_json: bool, limit: int
    ) -> list[InjectionCompletion]:
        """Get content completions matching the partial identifier."""
        completions: list[InjectionCompletion] = []

        for content in self._content_cache:
            # Match on UUID or content text preview
            id_lower = content.id.lower()
            text_lower = (content.content_text or "").lower()

            if partial in id_lower or partial in text_lower:
                # Build the insertion text (no quotes)
                if is_json:
                    text = f"@json({content.id})"
                    format_label = "JSON"
                else:
                    text = f"@text({content.id})"
                    format_label = "text"

                # Create preview from content text
                if content.content_text:
                    preview = content.content_text.replace("\n", " ").strip()
                else:
                    preview = "{structured}" if content.has_structured else "{empty}"

                description = f"{content.content_type_id} | {format_label}"

                completions.append(
                    InjectionCompletion(
                        text=text,
                        display_name=preview,
                        description=description,
                        injection_type="json" if is_json else "text",
                    )
                )

                if len(completions) >= limit:
                    break

        return completions

    def _get_schema_completions(
        self, partial: str, limit: int
    ) -> list[InjectionCompletion]:
        """Get schema completions matching the partial identifier."""
        completions: list[InjectionCompletion] = []

        for schema in self._schema_cache:
            name_lower = schema.schema_name.lower()
            desc_lower = (schema.description or "").lower()

            if partial in name_lower or partial in desc_lower:
                # Build insertion text (no quotes)
                text = f"@schema({schema.schema_name})"

                # Format description
                desc_preview = schema.description or "no description"
                description = f"{schema.schema_format_id} | {desc_preview}"

                completions.append(
                    InjectionCompletion(
                        text=text,
                        display_name=schema.schema_name,
                        description=description,
                        injection_type="schema",
                    )
                )

                if len(completions) >= limit:
                    break

        return completions

    @property
    def fragments(self) -> list[FragmentInfo]:
        """Get cached fragments."""
        return self._fragment_cache

    @property
    def content_parts(self) -> list[ContentPartInfo]:
        """Get cached content parts."""
        return self._content_cache

    @property
    def schemas(self) -> list[SchemaInfo]:
        """Get cached schemas."""
        return self._schema_cache
