"""Open Library search engine for books and literature."""

import html
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 OpenLibrarySearchEngine(BaseSearchEngine):
    """
    Open Library search engine for books and literature.

    Provides access to 2M+ books with metadata, covers, and reading lists.
    No authentication required. Part of the Internet Archive.
    """

    is_public = True
    is_generic = False
    is_scientific = False
    is_books = True  # New category for book search
    is_lexical = True
    needs_llm_relevance_filter = True

    def __init__(
        self,
        max_results: int = 10,
        sort: str = "relevance",
        language: Optional[str] = None,
        search_field: Optional[str] = None,
        llm: Optional[BaseLLM] = None,
        max_filtered_results: Optional[int] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """
        Initialize the Open Library search engine.

        Args:
            max_results: Maximum number of search results
            sort: Sort order ('relevance', 'new', 'old', 'random')
            language: Filter by language code (e.g., 'eng', 'fre', 'ger')
            search_field: Search in specific field ('title', 'author', 'subject')
            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.sort = sort
        self.language = language
        self.search_field = search_field

        self.base_url = "https://openlibrary.org"
        self.search_url = f"{self.base_url}/search.json"

        # User-Agent header is important for Open Library API
        # They may block requests without a proper User-Agent
        self.headers = {"User-Agent": USER_AGENT}

    def _build_query_params(self, query: str) -> Dict[str, Any]:
        """Build query parameters for the API request."""
        params = {
            "limit": min(self.max_results, 100),
            "fields": "key,title,author_name,author_key,first_publish_year,"
            "publisher,language,subject,isbn,cover_i,edition_count,"
            "ebook_access,has_fulltext,ia,description",
        }

        # Build query based on search field
        if self.search_field == "title":
            params["title"] = query
        elif self.search_field == "author":
            params["author"] = query
        elif self.search_field == "subject":
            params["subject"] = query
        else:
            params["q"] = query

        # Add sort if not relevance (default)
        if self.sort and self.sort != "relevance":
            params["sort"] = self.sort

        # Add language filter
        if self.language:
            params["language"] = self.language

        return params

    def _get_cover_url(
        self, cover_id: Optional[int], size: str = "M"
    ) -> Optional[str]:
        """Get cover image URL for a book."""
        if not cover_id:
            return None
        return f"https://covers.openlibrary.org/b/id/{cover_id}-{size}.jpg"

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

        Args:
            query: The search query

        Returns:
            List of preview dictionaries
        """
        logger.info(f"Getting Open Library 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()

            docs = data.get("docs", [])
            total_found = data.get("num_found", 0)
            logger.info(
                f"Found {total_found} Open Library results, returning {len(docs)}"
            )

            previews = []
            for doc in docs:
                try:
                    # Get work key and build URL
                    work_key = doc.get("key", "")
                    link = f"{self.base_url}{work_key}" if work_key else ""

                    # Get title (decode HTML entities)
                    title = html.unescape(doc.get("title", "Untitled"))

                    # Get authors
                    authors = doc.get("author_name", [])
                    if isinstance(authors, str):
                        authors = [authors]
                    authors = authors[:5]  # Limit to 5 authors

                    # Get first publish year
                    first_publish_year = doc.get("first_publish_year")

                    # Get publishers
                    publishers = doc.get("publisher", [])
                    if isinstance(publishers, str):
                        publishers = [publishers]
                    publisher = publishers[0] if publishers else ""

                    # Get subjects
                    subjects = doc.get("subject", [])
                    if isinstance(subjects, str):
                        subjects = [subjects]
                    subjects = subjects[:5]  # Limit to 5 subjects

                    # Get ISBNs
                    isbns = doc.get("isbn", [])
                    if isinstance(isbns, str):
                        isbns = [isbns]
                    isbn = isbns[0] if isbns else None

                    # Get cover
                    cover_id = doc.get("cover_i")
                    cover_url = self._get_cover_url(cover_id)

                    # Get description if available
                    description = doc.get("description", "")
                    # Description can be a string or a dict with "value" key
                    if isinstance(description, dict):
                        description = description.get("value", "")
                    elif isinstance(description, list):
                        description = (
                            " ".join(str(d) for d in description)
                            if description
                            else ""
                        )

                    # Build snippet with description for richer content
                    snippet_parts = []
                    if description:
                        snippet_parts.append(description[:800])
                    if authors:
                        snippet_parts.append(f"By {', '.join(authors[:3])}")
                    if first_publish_year:
                        snippet_parts.append(
                            f"First published: {first_publish_year}"
                        )
                    if subjects:
                        snippet_parts.append(
                            f"Subjects: {', '.join(subjects[:5])}"
                        )
                    snippet = ". ".join(snippet_parts)

                    # Check availability
                    has_fulltext = doc.get("has_fulltext", False)
                    ebook_access = doc.get("ebook_access", "no_ebook")
                    ia_ids = doc.get("ia", [])
                    if isinstance(ia_ids, str):
                        ia_ids = [ia_ids]

                    preview = {
                        "id": work_key,
                        "title": title,
                        "link": link,
                        "snippet": snippet,
                        "authors": authors,
                        "first_publish_year": first_publish_year,
                        "publisher": publisher,
                        "subjects": subjects,
                        "isbn": isbn,
                        "cover_url": cover_url,
                        "edition_count": doc.get("edition_count", 0),
                        "has_fulltext": has_fulltext,
                        "ebook_access": ebook_access,
                        "internet_archive_ids": ia_ids[:3] if ia_ids else [],
                        "source": "Open Library",
                        "_raw": doc,
                    }

                    previews.append(preview)

                except Exception:
                    logger.exception("Error parsing Open Library item")
                    continue

            return previews

        except (requests.RequestException, ValueError) as e:
            logger.exception("Open Library 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 Open Library books.

        Fetches detailed information from the Works API including
        full descriptions and excerpts.

        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)} Open Library books"
        )

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

            raw = item.get("_raw", {})
            if raw:
                # Get all languages
                languages = raw.get("language", [])
                if isinstance(languages, str):
                    languages = [languages]
                result["languages"] = languages

                # Get all subjects
                result["subjects"] = raw.get("subject", [])
                if isinstance(result["subjects"], str):
                    result["subjects"] = [result["subjects"]]

                # Get all publishers
                result["publishers"] = raw.get("publisher", [])
                if isinstance(result["publishers"], str):
                    result["publishers"] = [result["publishers"]]

                # Fetch detailed info from Works API
                work_key = item.get("id", "")
                work_data = self._fetch_work_details(work_key)

                # Build content with metadata + description + excerpts
                content_parts = []
                if result.get("authors"):
                    content_parts.append(
                        f"Authors: {', '.join(result['authors'])}"
                    )
                if result.get("first_publish_year"):
                    content_parts.append(
                        f"First published: {result['first_publish_year']}"
                    )
                if result.get("subjects"):
                    subjects = result["subjects"]
                    if isinstance(subjects, list):
                        content_parts.append(
                            f"Subjects: {', '.join(subjects[:10])}"
                        )

                # Use full description from Works API if available
                description = ""
                if work_data:
                    desc = work_data.get("description", "")
                    if isinstance(desc, dict):
                        desc = desc.get("value", "")
                    elif isinstance(desc, list):
                        desc = " ".join(str(d) for d in desc)
                    if isinstance(desc, str) and desc:
                        description = desc
                if not description:
                    desc = raw.get("description", "")
                    if isinstance(desc, dict):
                        desc = desc.get("value", "")
                    elif isinstance(desc, list):
                        desc = " ".join(str(d) for d in desc)
                    if isinstance(desc, str) and desc:
                        description = desc
                if description:
                    content_parts.append(f"\n{description}")

                # Add excerpts from Works API
                if work_data:
                    excerpts = work_data.get("excerpts", [])
                    if excerpts:
                        content_parts.append("\nExcerpts:")
                        for exc in excerpts[:5]:
                            text = exc.get("excerpt", "")
                            if text:
                                content_parts.append(f'  "{text}"')

                if result.get("has_fulltext"):
                    content_parts.append(
                        "\nFull text available on Internet Archive"
                    )

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

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

            results.append(result)

        return results

    def _fetch_work_details(self, work_key: str) -> Optional[Dict[str, Any]]:
        """Fetch detailed work information from the Works API."""
        if not work_key or not work_key.startswith("/works/"):
            if work_key:
                logger.warning(
                    "Invalid work_key format: expected '/works/...' prefix"
                )
            return None
        try:
            url = f"{self.base_url}{work_key}.json"
            response = safe_get(url, headers=self.headers, timeout=15)
            self._raise_if_rate_limit(response.status_code)
            response.raise_for_status()
            return response.json()  # type: ignore[no-any-return]
        except (RateLimitError, ValueError):
            raise
        except Exception:
            logger.warning(f"Failed to fetch work details for {work_key}")
            return None

    def get_book_by_isbn(self, isbn: str) -> Optional[Dict[str, Any]]:
        """
        Get a specific book by ISBN.

        Args:
            isbn: The book ISBN (10 or 13 digit)

        Returns:
            Book dictionary or None
        """
        try:
            url = f"{self.base_url}/isbn/{isbn}.json"
            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 book by ISBN {isbn}")
            return None

    def get_author(self, author_key: str) -> Optional[Dict[str, Any]]:
        """
        Get author information.

        Args:
            author_key: The author key (e.g., '/authors/OL23919A')

        Returns:
            Author dictionary or None
        """
        try:
            if not author_key or not author_key.startswith("/authors/"):
                logger.warning(
                    "Invalid author_key format: expected '/authors/...' prefix"
                )
                return None
            url = f"{self.base_url}{author_key}.json"
            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 author {author_key}")
            return None
