"""Example sentence fetcher using Tatoeba and Leipzig Corpora Collection.

Provides ExampleFetcher, which looks up example sentences for vocabulary
words. Tatoeba (API v0) and the Leipzig Wortschatz API are used as
interchangeable sources; either can be tried first with the other as
automatic fallback.

Tatoeba API docs: https://en.wiki.tatoeba.org/articles/show/api-v0
Leipzig API docs: https://api.wortschatz-leipzig.de/ws/swagger-ui/index.html
"""

import json
import re
import time
import urllib.parse
import urllib.request
from typing import Optional, Tuple


from rich.console import Console

_console = Console(stderr=True)


class ExampleFetcher:
    """Fetches example sentences from Tatoeba and/or Leipzig Corpora Collection.

    Either source can be tried first; the other is used as automatic fallback
    when the preferred source finds no matching sentence. The return type is
    identical regardless of which source supplied the result.

    Tatoeba requests are rate-limited to avoid overloading their servers.
    Leipzig has no rate-limit requirement.

    Attributes:
        TATOEBA_API_URL: Base URL for the Tatoeba API v0 search endpoint
        DEFAULT_DELAY: Default minimum delay between Tatoeba requests (seconds)
    """

    TATOEBA_API_URL = "https://tatoeba.org/eng/api_v0/search"
    DEFAULT_DELAY = 0.5  # seconds between Tatoeba requests

    def __init__(
        self,
        delay: float = DEFAULT_DELAY,
        verbose: bool = True,
    ):
        """Initialise the example fetcher.

        Args:
            delay: Minimum seconds between Tatoeba API requests (default: 0.5)
            verbose: Whether to print progress messages to stderr (default: True)
        """
        self.delay = delay
        self.verbose = verbose
        self._last_request_time = 0.0

        from .leipzig import LeipzigClient
        self._leipzig = LeipzigClient(verbose=verbose, timeout=10.0)

    def fetch_example(
        self,
        word: str,
        src_lang: str = "fin",
        target_lang: str = "eng",
        prefer: str = "tatoeba",
    ) -> str:
        """Fetch an example sentence containing the word.

        Args:
            word: The word to find in example sentences
            src_lang: Source language code (e.g., 'fin', 'swe', 'deu')
            target_lang: Target language code for translations (default: 'eng')
            prefer: Which source to try first — ``"tatoeba"`` (default) or
                    ``"leipzig"``. The other is tried as fallback.

        Returns:
            The source language sentence containing the word, or empty string
            if no suitable example is found.
        """
        result = self.fetch_example_with_metadata(word, src_lang, target_lang, prefer=prefer)
        return result[0] if result else ""

    def fetch_example_with_metadata(
        self,
        word: str,
        src_lang: str = "fin",
        target_lang: str = "eng",
        prefer: str = "tatoeba",
    ) -> Optional[Tuple[str, int, str, str, str]]:
        """Fetch an example sentence with metadata.

        Tries the preferred source first, falls back to the other. Leipzig is
        only called if the preferred/primary source found no matching sentence
        — not on error. Tatoeba errors propagate immediately without attempting
        Leipzig.

        Args:
            word: The word to find in example sentences
            src_lang: Source language code (e.g., 'fin', 'swe', 'deu')
            target_lang: Target language code for translations (default: 'eng')
            prefer: Which source to try first — ``"tatoeba"`` (default) or
                    ``"leipzig"``. The other is used as fallback.

        Returns:
            Tuple of (sentence_text, sentence_id, license, author, source)
            where source is ``"tatoeba"`` or ``"leipzig"``, or ``None`` if
            no suitable example is found anywhere.
        """
        if prefer == "leipzig":
            return self._fetch_prefer_leipzig(word, src_lang, target_lang)
        return self._fetch_prefer_tatoeba(word, src_lang, target_lang)

    def _fetch_prefer_tatoeba(
        self, word: str, src_lang: str, target_lang: str
    ) -> Optional[Tuple[str, int, str, str, str]]:
        """Try Tatoeba first, Leipzig as fallback."""
        self._rate_limit()

        params = urllib.parse.urlencode(
            {
                "from": src_lang,
                "query": word,
                "to": target_lang,
                "trans_filter": "limit",
                "trans_link": "direct",
            }
        )

        url = f"{self.TATOEBA_API_URL}?{params}"
        req = urllib.request.Request(url, headers={"User-Agent": "words-to-readlang/1.0"})

        try:
            with urllib.request.urlopen(req, timeout=10) as resp:
                data = json.loads(resp.read().decode("utf-8"))
        except Exception as exc:
            if self.verbose:
                _console.print(f"  [red]Tatoeba request failed:[/red] {exc}")
            return None

        for sentence in data.get("results", []):
            text = sentence.get("text", "")
            if self._whole_word_in(word, text):
                sentence_id = sentence.get("id", 0)
                license_info = sentence.get("license", "Unknown")
                author = sentence.get("user", {}).get("username", "Unknown")
                if self.verbose:
                    preview = text[:60] + "..." if len(text) > 60 else text
                    _console.print(f"  [green]✓[/green] [dim]{preview}[/dim] [cyan][Tatoeba][/cyan]")
                return (text, sentence_id, license_info, author, "tatoeba")

        # No match from Tatoeba — try Leipzig
        if self.verbose:
            _console.print(f"  [dim][Leipzig] trying fallback for '{word}'...[/dim]")
        leipzig_result = self._leipzig.fetch_example_with_metadata(word, src_lang)
        if leipzig_result:
            text, sentence_id, license_info, source_url = leipzig_result
            if self.verbose:
                preview = text[:60] + "..." if len(text) > 60 else text
                _console.print(f"  [green]✓[/green] [dim]{preview}[/dim] [yellow][Leipzig][/yellow]")
            return (text, sentence_id, license_info, source_url, "leipzig")

        if self.verbose:
            _console.print(f"  [red]✗[/red] [dim]not found[/dim]")
        return None

    def _fetch_prefer_leipzig(
        self, word: str, src_lang: str, target_lang: str
    ) -> Optional[Tuple[str, int, str, str, str]]:
        """Try Leipzig first, Tatoeba as fallback."""
        leipzig_result = self._leipzig.fetch_example_with_metadata(word, src_lang)
        if leipzig_result:
            text, sentence_id, license_info, source_url = leipzig_result
            if self.verbose:
                preview = text[:60] + "..." if len(text) > 60 else text
                _console.print(f"  [green]✓[/green] [dim]{preview}[/dim] [yellow][Leipzig][/yellow]")
            return (text, sentence_id, license_info, source_url, "leipzig")

        # No match from Leipzig — try Tatoeba
        if self.verbose:
            _console.print(f"  [dim][Tatoeba] trying fallback for '{word}'...[/dim]")
        self._rate_limit()

        params = urllib.parse.urlencode(
            {
                "from": src_lang,
                "query": word,
                "to": target_lang,
                "trans_filter": "limit",
                "trans_link": "direct",
            }
        )
        url = f"{self.TATOEBA_API_URL}?{params}"
        req = urllib.request.Request(url, headers={"User-Agent": "words-to-readlang/1.0"})

        try:
            with urllib.request.urlopen(req, timeout=10) as resp:
                data = json.loads(resp.read().decode("utf-8"))
        except Exception as exc:
            if self.verbose:
                _console.print(f"  [red]Tatoeba request failed:[/red] {exc}")
            return None

        for sentence in data.get("results", []):
            text = sentence.get("text", "")
            if self._whole_word_in(word, text):
                sentence_id = sentence.get("id", 0)
                license_info = sentence.get("license", "Unknown")
                author = sentence.get("user", {}).get("username", "Unknown")
                if self.verbose:
                    preview = text[:60] + "..." if len(text) > 60 else text
                    _console.print(f"  [green]✓[/green] [dim]{preview}[/dim] [cyan][Tatoeba][/cyan]")
                return (text, sentence_id, license_info, author, "tatoeba")

        if self.verbose:
            _console.print(f"  [red]✗[/red] [dim]not found[/dim]")
        return None

    def _whole_word_in(self, word: str, sentence: str) -> bool:
        """Check if word appears as a whole word in sentence.

        Uses word boundary regex to ensure the word appears as a complete
        token, not as part of another word.

        Args:
            word: The word to search for
            sentence: The sentence to search in

        Returns:
            True if the word appears as a whole word in the sentence

        Example:
            >>> client = ExampleFetcher()
            >>> client._whole_word_in("cat", "The cat sat")
            True
            >>> client._whole_word_in("cat", "concatenate")
            False
        """
        pattern = r"(?<!\w)" + re.escape(word) + r"(?!\w)"
        return bool(re.search(pattern, sentence))

    def _rate_limit(self) -> None:
        """Ensure minimum delay between requests.

        Implements a simple rate limiting mechanism to avoid overwhelming
        the Tatoeba API servers. Sleeps if necessary to maintain the
        configured delay between requests.
        """
        now = time.time()
        elapsed = now - self._last_request_time

        if elapsed < self.delay:
            time.sleep(self.delay - elapsed)

        self._last_request_time = time.time()
