import asyncio
import logging
from typing import Any
from urllib.parse import quote

from theHarvester.discovery.constants import MissingKey, get_delay
from theHarvester.lib.configuration import CredentialAdapter, FileSystemCredentialAdapter
from theHarvester.lib.core import AsyncFetcher
from theHarvester.parsers import myparser

logger = logging.getLogger(__name__)


class SearchBrave:
    """Search Brave while allowing credentials to be supplied without file access.

    Provider API:
    https://api-dashboard.search.brave.com/app/documentation/web-search/query

    Filesystem credentials remain the production default; injection keeps tests and
    embedded use independent of operator configuration files.
    """

    def __init__(self, word: str, limit: int, credential_adapter: CredentialAdapter | None = None) -> None:
        self.word = word
        self.results: list[dict[str, Any]] = []
        self.totalresults = ''
        credentials = credential_adapter if credential_adapter is not None else FileSystemCredentialAdapter()
        try:
            self.api_key = credentials.get('brave')
        except KeyError:
            raise MissingKey('Brave Search') from None
        if self.api_key is None or self.api_key == '':
            raise MissingKey('Brave Search')
        self.server = 'https://api.search.brave.com/res/v1/web/search'
        self.limit = limit
        self.proxy = False
        self.rate_limit_delay = 1  # Initial delay for rate limiting

    async def do_search(self):
        headers = {'Accept': 'application/json', 'Accept-Encoding': 'gzip', 'X-Subscription-Token': self.api_key}

        # Search queries: exact match and site-specific
        queries = [f'"{self.word}"', f'site:{self.word}']

        for query in queries:
            if len(self.results) >= self.limit:
                break
            try:
                for offset in range(10):
                    remaining = self.limit - len(self.results)
                    if remaining <= 0:
                        break
                    params = {
                        'q': query,
                        'count': min(20, remaining),
                        'offset': offset,
                        'safesearch': 'off',
                        'freshness': 'all',
                        'extra_snippets': 'true',  # Enable extra snippets for richer content
                        'text_decorations': 'true',  # Enable highlighting
                        'spellcheck': 'true',  # Enable spellcheck
                    }

                    # Build URL with parameters
                    param_string = '&'.join([f'{k}={quote(str(v))}' for k, v in params.items()])
                    url = f'{self.server}?{param_string}'

                    resp = await AsyncFetcher.fetch(url=url, headers=headers, proxy=self.proxy, json=True)

                    # Handle API response
                    if resp is None:
                        logger.info('No response received from Brave Search API')
                        break

                    # Check for API errors (rate limit, quota exceeded, etc.)
                    if 'error' in resp:
                        error_msg = resp.get('error', {}).get('message', 'Unknown API error')
                        error_code = resp.get('error', {}).get('code', 'unknown')

                        if 'rate limit' in error_msg.lower() or error_code == 'rate_limit_exceeded':
                            logger.info(f'Rate limit exceeded. Increasing delay to {self.rate_limit_delay * 2} seconds')
                            self.rate_limit_delay *= 2
                            await asyncio.sleep(self.rate_limit_delay)
                            break
                        elif 'quota' in error_msg.lower() or error_code == 'quota_exceeded':
                            logger.info('Brave Search API quota exceeded')
                            break
                        else:
                            break

                    if 'web' in resp and 'results' in resp['web']:
                        results = resp['web']['results'][:remaining]
                        if not results:
                            break

                        # Extract text content from results for parsing (including extra snippets)
                        for result in results:
                            result_text = f'{result.get("title", "")} {result.get("description", "")}'

                            # Add extra snippets if available
                            if 'extra_snippets' in result:
                                for snippet in result['extra_snippets']:
                                    result_text += f' {snippet}'

                            result_text += f' {result.get("url", "")}'
                            self.totalresults += result_text + '\n'

                        self.results.extend(results)

                        # Stop if we've reached our limit
                        if len(self.results) >= self.limit:
                            break
                        if not resp.get('query', {}).get('more_results_available', False):
                            break
                    else:
                        logger.info('Unexpected response format from Brave Search API')
                        break

                    await asyncio.sleep(get_delay())

            except Exception as e:
                error_msg = str(e).lower()

                # Handle specific API-related exceptions
                if 'rate limit' in error_msg or '429' in error_msg:
                    logger.info(f'Rate limit detected in exception. Increasing delay to {self.rate_limit_delay * 2} seconds')
                    self.rate_limit_delay *= 2
                    await asyncio.sleep(self.rate_limit_delay)
                elif 'quota' in error_msg or '403' in error_msg:
                    logger.info(f'Quota exceeded or access denied: {e}')
                    break
                elif 'timeout' in error_msg:
                    logger.info(f'Request timeout occurred: {e}')
                    await asyncio.sleep(get_delay() + 2)
                else:
                    logger.info(f'An exception has occurred in bravesearch: {e}')
                    await asyncio.sleep(get_delay() + 5)
                continue

    async def get_emails(self):
        rawres = myparser.Parser(self.totalresults, self.word)
        return await rawres.emails()

    async def get_hostnames(self):
        rawres = myparser.Parser(self.totalresults, self.word)
        return await rawres.hostnames()

    async def process(self, proxy=False):
        self.proxy = proxy
        await self.do_search()
