"""MCP tool for listing products.

This module implements the list_products tool following the service
layer pattern (ADR-006). The tool is a thin wrapper that:
1. Validates input with Pydantic
2. Extracts dependencies from server context (ADR-007)
3. Delegates to ProductService
4. Converts exceptions to user-friendly error format
"""

from typing import Annotated, Any, Literal

from fastmcp import Context
from fastmcp.exceptions import ToolError
from pydantic import BaseModel, Field, field_validator

from testio_mcp.exceptions import TestIOAPIError
from testio_mcp.server import mcp
from testio_mcp.services.product_service import ProductService
from testio_mcp.utilities import get_service
from testio_mcp.utilities.schema_utils import inline_schema_refs

# Type aliases for valid values (using Literal to avoid $defs in JSON schema)

ProductType = Literal["website", "mobile_app_ios", "mobile_app_android", "streaming_app"]

# Pydantic Models
# NOTE: Models use nested BaseModel classes for type safety and better FastAPI docs.
# Schemas are post-processed with inline_schema_refs() to avoid $ref resolution issues
# in some MCP clients like Gemini CLI 0.16.0.


class ProductSummary(BaseModel):
    """Summary information for a product.

    Attributes:
        product_id: Unique product identifier (integer, matches API)
        name: Product name
        type: Product type (e.g., 'website', 'mobile')
        description: Product description (optional)
    """

    product_id: int = Field(description="Product ID", alias="id")
    name: str = Field(description="Product name")
    type: str = Field(description="Product type (e.g., 'website', 'mobile')")
    description: str | None = Field(
        default=None,
        description="Product description",
    )

    @field_validator("product_id", mode="before")
    @classmethod
    def coerce_id_to_int(cls, v: Any) -> int:
        """Convert product ID to integer (accepts string or int input).

        Args:
            v: Product ID value (int or str)

        Returns:
            Product ID as integer

        Raises:
            ValueError: If value cannot be converted to integer
        """
        return int(v)


class ListProductsOutput(BaseModel):
    """Output model for list_products tool.

    Attributes:
        total_count: Total number of products after filtering
        filters_applied: Dictionary of filters that were applied
        products: List of product summaries
    """

    total_count: int = Field(
        description="Total number of products after filtering",
        ge=0,
    )
    filters_applied: dict[str, str | list[str] | None] = Field(
        description="Filters applied (search, product_type)",
    )
    products: list[ProductSummary] = Field(
        description="List of products with id, name, type, description",
    )


# MCP Tool


@mcp.tool(output_schema=inline_schema_refs(ListProductsOutput.model_json_schema()))
async def list_products(
    ctx: Context,
    search: Annotated[
        str | None,
        Field(
            description="Search term to filter products by name or description (case-insensitive).",
        ),
    ] = None,
    product_type: Annotated[
        str | list[ProductType] | None,
        Field(
            description=(
                "Filter by product type. "
                'Pass as comma-separated string: product_type="website,mobile_app_ios" '
                'or JSON array: product_type=["website", "mobile_app_ios"]. '
                "Valid values: website, mobile_app_ios, mobile_app_android, streaming_app. "
                'Common patterns: "website" for web apps, '
                '"mobile_app_ios,mobile_app_android" for all mobile apps. '
                "Omit to return all product types."
            ),
            json_schema_extra={
                "examples": [
                    "website",  # Web applications (string)
                    "mobile_app_ios,mobile_app_android",  # All mobile apps (comma-separated)
                    ["website"],  # Web applications (array)
                    ["mobile_app_ios", "mobile_app_android"],  # All mobile apps (array)
                ]
            },
        ),
    ] = None,
) -> dict[str, Any]:
    """List all products with optional search and type filtering.

    Valid product types: website, mobile_app_ios, mobile_app_android, streaming_app

    Use to discover product IDs for other tools.
    """
    # Create service instance using helper (eliminates 5 lines of boilerplate)
    service = get_service(ctx, ProductService)

    # Delegate to service and convert exceptions to MCP error format
    try:
        # Parse comma-separated string to list if needed (AI-friendly format)
        from typing import cast

        product_types: list[str] | None = None
        if product_type is not None:
            if isinstance(product_type, str):
                # Parse comma-separated: "website,mobile_app_ios" → list
                product_types = [t.strip() for t in product_type.split(",")]
            else:
                # Already a list (cast for mypy: Literal types are strings)
                product_types = cast(list[str], product_type)

        result = await service.list_products(
            search=search,
            product_type=product_types,
        )

        # Validate output with Pydantic
        # This ensures API response matches expected structure
        validated = ListProductsOutput(**result)
        return validated.model_dump(by_alias=True, exclude_none=True)

    except TestIOAPIError as e:
        # Convert API error to ToolError with user-friendly message
        raise ToolError(
            f"❌ API error: {e.message}\n"
            f"ℹ️  HTTP status code: {e.status_code}\n"
            f"💡 Check API status and authentication. If the problem persists, contact support."
        ) from e

    except Exception as e:
        # Catch-all for unexpected errors
        raise ToolError(
            f"❌ Unexpected error: {str(e)}\n"
            f"ℹ️  An unexpected error occurred while fetching products\n"
            f"💡 Please try again or contact support if the problem persists"
        ) from e
