# Copyright (c) 2025 Microsoft Corporation.
# Licensed under the MIT License

"""Wix MCP handler for keyword-based content retrieval.

Queries Wix sites directly via their MCP endpoint
(https://{site}/_api/mcp) using SearchInSite and SearchSiteApiDocs tools.
Results are converted to schema.org format (Product, Article, WebPage) for ranking.
"""

import json
import logging
from typing import Any

import aiohttp
from nlweb_core.retrieved_item import RetrievedItem

from nlweb_platform_handlers.base import KeywordRetrievalHandler

logger = logging.getLogger(__name__)


class WixMCPHandler(KeywordRetrievalHandler):
    """Handler for Wix sites using MCP for retrieval.

    Queries Wix sites via their MCP endpoint using SearchSiteApiDocs
    (better for products/services) with fallback to SearchInSite
    (general content).

    Configure in config.yaml:
        site_config:
          handler:
            sites:
              username.wixsite.com/mysite:
                ask_handler_class: WixMCPHandler
                ask_handler_import_path: nlweb_platform_handlers.wix
    """

    async def _retrieve_items(
        self,
        query: str,
        site: str | None,
        num_results: int,
    ) -> list[RetrievedItem]:
        """Retrieve content from Wix via MCP."""
        if not site:
            logger.error("No site specified for Wix MCP search")
            return []

        endpoint = f"https://{site}/_api/mcp"

        # Try SearchSiteApiDocs first (better for products/services),
        # then fall back to SearchInSite for general content
        results = await self._search_with_tool(
            endpoint, "SearchSiteApiDocs", query, site
        )

        if not results:
            results = await self._search_with_tool(
                endpoint, "SearchInSite", query, site
            )

        return results[:num_results]

    async def _search_with_tool(
        self,
        endpoint: str,
        tool_name: str,
        query: str,
        site: str,
    ) -> list[RetrievedItem]:
        """Execute a search using the specified MCP tool."""
        mcp_request = {
            "jsonrpc": "2.0",
            "method": "tools/call",
            "params": {
                "name": tool_name,
                "arguments": {
                    "searchTerm": query,
                },
            },
            "id": 1,
        }

        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(
                    endpoint,
                    json=mcp_request,
                    headers={"Content-Type": "application/json"},
                    timeout=aiohttp.ClientTimeout(total=30),
                ) as response:
                    if response.status != 200:
                        logger.error(
                            f"Wix MCP {tool_name} request failed with status {response.status}"
                        )
                        return []

                    try:
                        result = await response.json(content_type=None)
                    except Exception as e:
                        logger.error(f"Failed to parse Wix MCP response as JSON: {e}")
                        return []

                    if "error" in result:
                        logger.debug(f"Wix MCP {tool_name} error: {result['error']}")
                        return []

                    return self._parse_mcp_response(result, site, tool_name)

        except aiohttp.ClientError as e:
            logger.error(f"Wix MCP request failed: {e}")
            return []
        except Exception as e:
            logger.error(f"Unexpected error in Wix MCP request: {e}")
            return []

    def _parse_mcp_response(
        self, result: dict[str, Any], site: str, tool_name: str
    ) -> list[RetrievedItem]:
        """Parse MCP JSON-RPC response into RetrievedItem list."""
        retrieved_items: list[RetrievedItem] = []

        try:
            mcp_result = result.get("result", {})

            if mcp_result.get("isError"):
                logger.debug(f"Wix MCP {tool_name} returned error in response")
                return []

            # Handle standard MCP content array format
            if "content" in mcp_result and isinstance(mcp_result["content"], list):
                for content_item in mcp_result["content"]:
                    if content_item.get("type") == "text" and "text" in content_item:
                        text = content_item["text"]
                        # Handle "Site search tool returned: {...}" wrapper
                        if text.startswith("Site search tool returned:"):
                            text = text[len("Site search tool returned:") :].strip()
                        try:
                            search_data = json.loads(text)
                            if "result" in search_data:
                                search_data = search_data["result"]
                            items = self._format_results(search_data, site)
                            retrieved_items.extend(items)
                        except json.JSONDecodeError:
                            # Plain text content — wrap as a WebPage item
                            item = self._text_to_item(content_item["text"], site)
                            if item:
                                retrieved_items.append(item)

            # Handle direct result format
            elif isinstance(mcp_result, dict):
                items = self._format_results(mcp_result, site)
                retrieved_items.extend(items)

            logger.info(
                f"Retrieved {len(retrieved_items)} items from Wix MCP {tool_name}"
            )

        except Exception as e:
            logger.error(f"Error parsing Wix MCP response: {e}")

        return retrieved_items

    def _format_results(
        self, search_data: dict[str, Any], site: str
    ) -> list[RetrievedItem]:
        """Convert Wix search results to RetrievedItem list."""
        retrieved_items: list[RetrievedItem] = []

        # Wix returns results in various formats depending on content type
        items: list[dict[str, Any]] = []
        for key in ["results", "items", "products", "documents", "pages"]:
            if key in search_data and isinstance(search_data[key], list):
                items = search_data[key]
                break

        if not items and search_data:
            items = [search_data]

        for item in items:
            schema_object = self._item_to_schema(item, site)
            url = (
                self._make_absolute_url(item.get("url") or item.get("link"), site)
                or f"https://{site}"
            )

            retrieved_items.append(
                RetrievedItem(
                    url=url,
                    raw_schema_object=schema_object,
                    site=site,
                )
            )

        return retrieved_items

    def _make_absolute_url(self, url: str | None, site: str) -> str | None:
        """Convert a relative URL to an absolute URL."""
        if not url:
            return None
        url = url.strip()
        if url.startswith(("http://", "https://")):
            return url
        if url.startswith("//"):
            return f"https:{url}"
        if url.startswith("/"):
            return f"https://{site}{url}"
        return f"https://{site}/{url}"

    def _wix_media_to_url(self, media_id: str | None) -> str | None:
        """Convert a Wix media ID to a full static CDN URL.

        Wix MCP returns media IDs like "38036e_xxx~mv2.jpg" that need
        conversion to https://static.wixstatic.com/media/{id}.
        """
        if not media_id:
            return None
        media_id = media_id.strip()
        if media_id.startswith(("http://", "https://")):
            return media_id
        return f"https://static.wixstatic.com/media/{media_id}"

    def _item_to_schema(self, item: dict[str, Any], site: str) -> dict[str, Any]:
        """Convert a Wix item to the appropriate schema.org type."""
        if any(key in item for key in ["price", "sku", "inventory", "variants"]):
            return self._product_to_schema(item, site)
        elif any(key in item for key in ["body", "content", "publishedDate"]):
            return self._article_to_schema(item, site)
        else:
            return self._webpage_to_schema(item, site)

    def _set_image_from_wix_media(
        self, schema_object: dict[str, Any], item: dict[str, Any]
    ) -> None:
        """Set image field from Wix media IDs (img or images fields)."""
        if "image" in schema_object:
            return
        if img := item.get("img"):
            schema_object["image"] = self._wix_media_to_url(img)
        elif images := item.get("images"):
            if isinstance(images, list) and images:
                schema_object["image"] = self._wix_media_to_url(images[0])

    def _set_url_fields(
        self,
        schema_object: dict[str, Any],
        item: dict[str, Any],
        site: str,
        mappings: list[tuple[str, str]],
    ) -> None:
        """Set URL fields with absolute URL conversion."""
        for wix_key, schema_key in mappings:
            if value := item.get(wix_key):
                if schema_key not in schema_object:
                    schema_object[schema_key] = self._make_absolute_url(value, site)

    def _product_to_schema(self, item: dict[str, Any], site: str) -> dict[str, Any]:
        """Convert Wix product to schema.org Product."""
        schema_object: dict[str, Any] = {
            "@context": "https://schema.org",
            "@type": "Product",
        }

        for wix_key, schema_key in [
            ("name", "name"),
            ("title", "name"),
            ("description", "description"),
            ("sku", "sku"),
        ]:
            if value := item.get(wix_key):
                if schema_key not in schema_object:
                    schema_object[schema_key] = value

        self._set_image_from_wix_media(schema_object, item)
        self._set_url_fields(
            schema_object,
            item,
            site,
            [
                ("image", "image"),
                ("imageUrl", "image"),
                ("url", "url"),
                ("link", "url"),
            ],
        )

        if price := item.get("price"):
            schema_object["offers"] = {
                "@type": "Offer",
                "price": price,
                "priceCurrency": item.get("currency", "USD"),
            }

        return schema_object

    def _article_to_schema(self, item: dict[str, Any], site: str) -> dict[str, Any]:
        """Convert Wix article/blog post to schema.org Article."""
        schema_object: dict[str, Any] = {
            "@context": "https://schema.org",
            "@type": "Article",
        }

        for wix_key, schema_key in [
            ("title", "headline"),
            ("name", "headline"),
            ("body", "articleBody"),
            ("content", "articleBody"),
            ("description", "description"),
            ("excerpt", "description"),
            ("publishedDate", "datePublished"),
            ("publishDate", "datePublished"),
            ("author", "author"),
        ]:
            if value := item.get(wix_key):
                if schema_key not in schema_object:
                    schema_object[schema_key] = value

        self._set_image_from_wix_media(schema_object, item)
        self._set_url_fields(
            schema_object,
            item,
            site,
            [
                ("image", "image"),
                ("imageUrl", "image"),
                ("url", "url"),
                ("link", "url"),
            ],
        )

        return schema_object

    def _webpage_to_schema(self, item: dict[str, Any], site: str) -> dict[str, Any]:
        """Convert generic Wix item to schema.org WebPage."""
        schema_object: dict[str, Any] = {
            "@context": "https://schema.org",
            "@type": "WebPage",
        }

        for wix_key, schema_key in [
            ("title", "name"),
            ("name", "name"),
            ("description", "description"),
            ("content", "text"),
            ("body", "text"),
        ]:
            if value := item.get(wix_key):
                if schema_key not in schema_object:
                    schema_object[schema_key] = value

        self._set_image_from_wix_media(schema_object, item)
        self._set_url_fields(
            schema_object,
            item,
            site,
            [
                ("image", "image"),
                ("imageUrl", "image"),
                ("url", "url"),
                ("link", "url"),
            ],
        )

        return schema_object

    def _text_to_item(self, text: str, site: str) -> RetrievedItem | None:
        """Create a RetrievedItem from plain text response."""
        if not text or not text.strip():
            return None

        schema_object: dict[str, Any] = {
            "@context": "https://schema.org",
            "@type": "WebPage",
            "text": text,
            "name": text[:100] + "..." if len(text) > 100 else text,
        }

        return RetrievedItem(
            url=f"https://{site}",
            raw_schema_object=schema_object,
            site=site,
        )
