"""Project Gutenberg search engine via Gutendex API."""

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 GutenbergSearchEngine(BaseSearchEngine):
    """
    Project Gutenberg search engine via Gutendex API.

    Provides access to 70,000+ free public domain books with full text.
    No authentication required.
    """

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

    def __init__(
        self,
        max_results: int = 10,
        languages: Optional[str] = None,
        topic: Optional[str] = None,
        author_year_start: Optional[int] = None,
        author_year_end: Optional[int] = None,
        copyright_filter: Optional[bool] = None,
        sort: str = "popular",
        max_content_chars: int = 50000,
        llm: Optional[BaseLLM] = None,
        max_filtered_results: Optional[int] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """
        Initialize the Project Gutenberg search engine.

        Args:
            max_results: Maximum number of search results
            languages: Filter by language codes (e.g., 'en', 'fr,de')
            topic: Filter by subject/bookshelf topic
            author_year_start: Filter authors born after this year
            author_year_end: Filter authors born before this year
            copyright_filter: Filter by copyright status (True/False/None)
            sort: Sort order ('popular', 'ascending', 'descending')
            max_content_chars: Maximum characters of book text to retrieve
            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.languages = languages
        self.topic = topic
        self.author_year_start = author_year_start
        self.author_year_end = author_year_end
        self.copyright_filter = copyright_filter
        self.sort = sort
        self.max_content_chars = max_content_chars

        self.base_url = "https://gutendex.com"
        self.search_url = f"{self.base_url}/books/"

        # 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: Dict[str, Any] = {}
        if query:
            params["search"] = query

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

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

        if self.author_year_start is not None:
            params["author_year_start"] = self.author_year_start

        if self.author_year_end is not None:
            params["author_year_end"] = self.author_year_end

        if self.copyright_filter is not None:
            params["copyright"] = str(self.copyright_filter).lower()

        if self.sort and self.sort != "popular":
            params["sort"] = self.sort

        return params

    def _get_best_format_url(self, formats: Dict[str, str]) -> Optional[str]:
        """Get the best available format URL for reading."""
        # Priority order for reading formats
        priority = [
            "text/html",
            "text/html; charset=utf-8",
            "text/plain; charset=utf-8",
            "text/plain",
            "application/epub+zip",
            "application/x-mobipocket-ebook",
            "application/pdf",
        ]

        for mime_type in priority:
            if mime_type in formats:
                return formats[mime_type]

        # Return first available if no priority match
        if formats:
            return next(iter(formats.values()))
        return None

    def _get_text_url(self, formats: Dict[str, str]) -> Optional[str]:
        """Get the plain text URL for content retrieval."""
        for mime_type in [
            "text/plain; charset=utf-8",
            "text/plain; charset=us-ascii",
            "text/plain",
        ]:
            if mime_type in formats:
                return formats[mime_type]
        return None

    def _fetch_book_text(self, text_url: str) -> Optional[str]:
        """Fetch and return the plain text content of a book."""
        try:
            response = safe_get(text_url, headers=self.headers, timeout=30)
            self._raise_if_rate_limit(response.status_code)
            response.raise_for_status()

            text = response.text
            if not text:
                return None

            # Strip the Project Gutenberg header/footer boilerplate
            start_markers = [
                "*** START OF THE PROJECT GUTENBERG EBOOK",
                "*** START OF THIS PROJECT GUTENBERG EBOOK",
                "***START OF THE PROJECT GUTENBERG EBOOK",
            ]
            end_markers = [
                "*** END OF THE PROJECT GUTENBERG EBOOK",
                "*** END OF THIS PROJECT GUTENBERG EBOOK",
                "***END OF THE PROJECT GUTENBERG EBOOK",
            ]

            for marker in start_markers:
                idx = text.find(marker)
                if idx != -1:
                    # Skip past the marker line
                    newline = text.find("\n", idx)
                    if newline != -1:
                        text = text[newline + 1 :]
                    break

            for marker in end_markers:
                idx = text.find(marker)
                if idx != -1:
                    text = text[:idx]
                    break

            text = text.strip()

            # Truncate to max_content_chars
            if len(text) > self.max_content_chars:
                text = (
                    text[: self.max_content_chars] + "\n\n[... truncated ...]"
                )

            return text

        except (RateLimitError, ValueError):
            raise
        except Exception:
            logger.warning(f"Failed to fetch book text from {text_url}")
            return None

    def _parse_authors(self, authors: List[Dict]) -> List[str]:
        """Parse author information."""
        result = []
        for author in authors[:5]:
            name = author.get("name", "")
            if name:
                # Format: "Last, First" -> "First Last"
                if ", " in name:
                    parts = name.split(", ", 1)
                    name = f"{parts[1]} {parts[0]}"
                result.append(name)
        return result

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

        Args:
            query: The search query

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

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

            previews = []
            for book in results[: self.max_results]:
                try:
                    book_id = book.get("id")
                    title = book.get("title", "Untitled")

                    # Get authors
                    authors = self._parse_authors(book.get("authors", []))

                    # Get subjects and bookshelves
                    subjects = book.get("subjects", [])[:5]
                    bookshelves = book.get("bookshelves", [])[:3]

                    # Get languages
                    languages = book.get("languages", [])

                    # Get formats
                    formats = book.get("formats", {})
                    read_url = self._get_best_format_url(formats)

                    # Build Gutenberg URL
                    gutenberg_url = (
                        f"https://www.gutenberg.org/ebooks/{book_id}"
                    )

                    # Get summaries if available
                    summaries = book.get("summaries", [])
                    summary_text = ""
                    if summaries and isinstance(summaries, list):
                        # Use the first summary, strip whitespace
                        first_summary = summaries[0] if summaries else ""
                        if isinstance(first_summary, str):
                            summary_text = first_summary.strip()[:300]

                    # Build snippet with summary for richer content
                    snippet_parts = []
                    if summary_text:
                        snippet_parts.append(summary_text)
                    if authors:
                        snippet_parts.append(f"By {', '.join(authors[:2])}")
                    if subjects and not summary_text:
                        snippet_parts.append(
                            f"Subjects: {', '.join(subjects[:3])}"
                        )
                    if bookshelves and not summary_text:
                        snippet_parts.append(
                            f"Bookshelves: {', '.join(bookshelves[:2])}"
                        )
                    snippet = ". ".join(snippet_parts)

                    # Check for cover image
                    cover_url = formats.get("image/jpeg")

                    preview = {
                        "id": str(book_id),
                        "title": title,
                        "link": gutenberg_url,
                        "snippet": snippet,
                        "authors": authors,
                        "subjects": subjects,
                        "bookshelves": bookshelves,
                        "languages": languages,
                        "download_count": book.get("download_count", 0),
                        "read_url": read_url,
                        "cover_url": cover_url,
                        "formats": list(formats.keys()),
                        "copyright": book.get("copyright", False),
                        "source": "Project Gutenberg",
                        "_raw": book,
                    }

                    previews.append(preview)

                except Exception:
                    logger.exception("Error parsing Gutenberg book")
                    continue

            return previews

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

        Fetches the actual plain text of each book from Project Gutenberg.

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

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

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

                # Get all bookshelves
                result["bookshelves"] = raw.get("bookshelves", [])

                # Get translators
                translators = raw.get("translators", [])
                result["translators"] = self._parse_authors(translators)

                # Fetch actual book text
                formats = raw.get("formats", {})
                text_url = self._get_text_url(formats)

                book_text = None
                if text_url and text_url.startswith(
                    "https://www.gutenberg.org/"
                ):
                    logger.info(
                        f"Fetching book text for '{result.get('title')}' from {text_url}"
                    )
                    book_text = self._fetch_book_text(text_url)
                elif text_url:
                    logger.warning(
                        f"Skipping text_url with unexpected origin: {text_url}"
                    )

                # Build content with metadata header + actual text
                content_parts = []
                if result.get("authors"):
                    content_parts.append(
                        f"Authors: {', '.join(result['authors'])}"
                    )
                if result.get("subjects"):
                    content_parts.append(
                        f"Subjects: {', '.join(result['subjects'][:5])}"
                    )

                if book_text:
                    content_parts.append("")
                    content_parts.append(book_text)
                    logger.info(
                        f"Retrieved {len(book_text)} chars of text for '{result.get('title')}'"
                    )
                else:
                    if result.get("bookshelves"):
                        content_parts.append(
                            f"Bookshelves: {', '.join(result['bookshelves'])}"
                        )
                    if result.get("download_count"):
                        content_parts.append(
                            f"Downloads: {result['download_count']}"
                        )
                    if result.get("read_url"):
                        content_parts.append(
                            f"Read online: {result['read_url']}"
                        )
                    logger.warning(
                        f"Could not fetch text for '{result.get('title')}', using metadata only"
                    )

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

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

            results.append(result)

        return results

    def get_book(self, book_id: int) -> Optional[Dict[str, Any]]:
        """
        Get a specific book by Gutenberg ID.

        Args:
            book_id: The Project Gutenberg book ID

        Returns:
            Book dictionary or None
        """
        try:
            url = f"{self.base_url}/books/{book_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 Gutenberg book {book_id}")
            return None

    def search_by_topic(self, topic: str) -> List[Dict[str, Any]]:
        """
        Search books by topic/subject.

        Args:
            topic: The topic to search for

        Returns:
            List of matching books
        """
        original_topic = self.topic
        try:
            self.topic = topic
            return self.run("")
        finally:
            self.topic = original_topic
