"""Zenodo search engine for open research data and publications."""

import html
import re
from typing import Any, Dict, List, Optional

import requests
from langchain_core.language_models import BaseLLM
from loguru import logger

from ...constants import USER_AGENT
from ...security.safe_requests import safe_get
from ..rate_limiting import RateLimitError
from ..search_engine_base import BaseSearchEngine


class ZenodoSearchEngine(BaseSearchEngine):
    """
    Zenodo search engine for open research data and publications.

    Provides access to millions of research outputs including datasets,
    software, publications, and more. No authentication required for search.
    """

    is_public = True
    is_generic = False
    is_scientific = True
    is_code = False
    is_lexical = True
    needs_llm_relevance_filter = True

    def __init__(
        self,
        max_results: int = 10,
        resource_type: Optional[str] = None,
        access_right: Optional[str] = None,
        communities: Optional[str] = None,
        sort: str = "bestmatch",
        llm: Optional[BaseLLM] = None,
        max_filtered_results: Optional[int] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """
        Initialize the Zenodo search engine.

        Args:
            max_results: Maximum number of search results
            resource_type: Filter by type (dataset, software, publication, etc.)
            access_right: Filter by access (open, closed, embargoed, restricted)
            communities: Filter by Zenodo community
            sort: Sort order (bestmatch, mostrecent, -mostrecent)
            llm: Language model for relevance filtering
            max_filtered_results: Maximum results after filtering
            settings_snapshot: Settings snapshot for thread context
        """
        super().__init__(
            llm=llm,
            max_filtered_results=max_filtered_results,
            max_results=max_results,
            settings_snapshot=settings_snapshot,
            **kwargs,
        )

        self.resource_type = resource_type
        self.access_right = access_right
        self.communities = communities
        self.sort = sort

        self.base_url = "https://zenodo.org"
        self.search_url = f"{self.base_url}/api/records"

        # User-Agent header for API requests
        self.headers = {"User-Agent": USER_AGENT}

    def _build_query_params(self, query: str) -> Dict[str, Any]:
        """Build query parameters for the API request."""
        params = {
            "q": query,
            "size": min(self.max_results, 100),
            "sort": self.sort,
        }

        if self.resource_type:
            params["type"] = self.resource_type

        if self.access_right:
            params["access_right"] = self.access_right

        if self.communities:
            params["communities"] = self.communities

        return params

    def _parse_creators(self, creators: List[Dict]) -> List[str]:
        """Parse creator/author information."""
        result = []
        for creator in creators[:5]:
            name = creator.get("name", "")
            if name:
                result.append(name)
        return result

    def _get_resource_type_label(self, resource_type: Dict) -> str:
        """Get human-readable resource type label."""
        if not resource_type:
            return "Unknown"
        return (
            resource_type.get("title") or resource_type.get("type") or "Unknown"
        )

    def _get_previews(self, query: str) -> List[Dict[str, Any]]:
        """
        Get preview information for Zenodo records.

        Args:
            query: The search query

        Returns:
            List of preview dictionaries
        """
        logger.info(f"Getting Zenodo previews for query: {query}")

        # Apply rate limiting
        self._last_wait_time = self.rate_tracker.apply_rate_limit(
            self.engine_type
        )

        try:
            params = self._build_query_params(query)
            response = safe_get(
                self.search_url,
                params=params,
                headers=self.headers,
                timeout=30,
            )

            self._raise_if_rate_limit(response.status_code)

            response.raise_for_status()
            data = response.json()

            hits = data.get("hits", {})
            results = hits.get("hits", [])
            total = hits.get("total", 0)
            logger.info(
                f"Found {total} Zenodo results, returning {len(results)}"
            )

            previews = []
            for record in results[: self.max_results]:
                try:
                    record_id = record.get("id")
                    metadata = record.get("metadata", {})

                    title = metadata.get("title", "Untitled")

                    # Get creators
                    creators = self._parse_creators(
                        metadata.get("creators", [])
                    )

                    # Get description/abstract
                    description = metadata.get("description", "")
                    # Strip HTML tags and decode entities for snippet
                    if description:
                        description = html.unescape(
                            re.sub(r"<[^>]+>", "", description)
                        )
                        description = description[:500]

                    # Get DOI
                    doi = metadata.get("doi", "")

                    # Get publication date
                    pub_date = metadata.get("publication_date", "")

                    # Get resource type
                    resource_type = metadata.get("resource_type", {})
                    type_label = self._get_resource_type_label(resource_type)

                    # Get access right
                    access = metadata.get("access_right", "open")

                    # Get keywords
                    keywords = metadata.get("keywords", [])[:10]

                    # Get license
                    license_info = metadata.get("license", {})
                    license_id = (
                        license_info.get("id", "") if license_info else ""
                    )

                    # Get links
                    links = record.get("links", {})
                    record_url = links.get(
                        "self_html", f"{self.base_url}/records/{record_id}"
                    )
                    doi_url = links.get("doi", "")

                    # Build snippet
                    snippet_parts = []
                    if creators:
                        snippet_parts.append(f"By {', '.join(creators[:2])}")
                    if type_label:
                        type_str = f"Type: {type_label}"
                        # Add access status and license inline
                        access_license = []
                        if access:
                            access_license.append(
                                access.replace("_", " ").title()
                            )
                        if license_id:
                            access_license.append(license_id.upper())
                        if access_license:
                            type_str += f" ({', '.join(access_license)})"
                        snippet_parts.append(type_str)
                    if pub_date:
                        snippet_parts.append(f"Published: {pub_date}")
                    if description:
                        snippet_parts.append(description[:200])
                    snippet = ". ".join(snippet_parts)

                    preview = {
                        "id": str(record_id),
                        "title": title,
                        "link": record_url,
                        "snippet": snippet,
                        "authors": creators,
                        "doi": doi,
                        "doi_url": doi_url,
                        "publication_date": pub_date,
                        "resource_type": type_label,
                        "access_right": access,
                        "keywords": keywords,
                        "license": license_id,
                        "description": description,
                        "source": "Zenodo",
                        "_raw": record,
                    }

                    previews.append(preview)

                except Exception:
                    logger.exception("Error parsing Zenodo record")
                    continue

            return previews

        except (requests.RequestException, ValueError) as e:
            logger.exception("Zenodo API request failed")
            self._raise_if_rate_limit(e)
            return []

    def _get_full_content(
        self, relevant_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Get full content for the relevant Zenodo records.

        Args:
            relevant_items: List of relevant preview dictionaries

        Returns:
            List of result dictionaries with full content
        """
        logger.info(
            f"Getting full content for {len(relevant_items)} Zenodo records"
        )

        results = []
        for item in relevant_items:
            result = item.copy()

            raw = item.get("_raw", {})
            if raw:
                metadata = raw.get("metadata", {})

                # Get full description (strip HTML tags and decode entities)
                desc = metadata.get("description", "")
                if desc:
                    desc = html.unescape(re.sub(r"<[^>]+>", "", desc))
                result["description"] = desc

                # Get all keywords
                result["keywords"] = metadata.get("keywords", [])

                # Get related identifiers
                result["related_identifiers"] = metadata.get(
                    "related_identifiers", []
                )

                # Get files info
                files = raw.get("files") or []
                result["files"] = [
                    {
                        "filename": f.get("key", ""),
                        "size": f.get("size", 0),
                        "checksum": f.get("checksum", ""),
                    }
                    for f in files[:10]
                ]

                # Get references
                result["references"] = metadata.get("references", [])

                # Build content summary
                content_parts = []
                if result.get("authors"):
                    content_parts.append(
                        f"Authors: {', '.join(result['authors'])}"
                    )
                if result.get("resource_type"):
                    content_parts.append(f"Type: {result['resource_type']}")
                if result.get("publication_date"):
                    content_parts.append(
                        f"Published: {result['publication_date']}"
                    )
                if result.get("doi"):
                    content_parts.append(f"DOI: {result['doi']}")
                if result.get("keywords"):
                    content_parts.append(
                        f"Keywords: {', '.join(str(k) for k in result['keywords'][:5])}"
                    )
                if result.get("license"):
                    content_parts.append(f"License: {result['license']}")
                if result.get("description"):
                    content_parts.append(
                        f"\nDescription: {result['description'][:1000]}"
                    )

                result["content"] = "\n".join(content_parts)

            # Clean up internal fields
            if "_raw" in result:
                del result["_raw"]

            results.append(result)

        return results

    def get_record(self, record_id: int) -> Optional[Dict[str, Any]]:
        """
        Get a specific record by Zenodo ID.

        Args:
            record_id: The Zenodo record ID

        Returns:
            Record dictionary or None
        """
        try:
            url = f"{self.search_url}/{record_id}"
            response = safe_get(url, headers=self.headers, timeout=30)
            self._raise_if_rate_limit(response.status_code)
            response.raise_for_status()
            return response.json()  # type: ignore[no-any-return]
        except RateLimitError:
            raise
        except Exception:
            logger.exception(f"Error fetching Zenodo record {record_id}")
            return None

    def search_datasets(self, query: str) -> List[Dict[str, Any]]:
        """
        Search specifically for datasets.

        Args:
            query: The search query

        Returns:
            List of matching datasets
        """
        original_type = self.resource_type
        try:
            self.resource_type = "dataset"
            return self.run(query)
        finally:
            self.resource_type = original_type

    def search_software(self, query: str) -> List[Dict[str, Any]]:
        """
        Search specifically for software.

        Args:
            query: The search query

        Returns:
            List of matching software records
        """
        original_type = self.resource_type
        try:
            self.resource_type = "software"
            return self.run(query)
        finally:
            self.resource_type = original_type
