import json
import os
from typing import Dict, List, Optional
from urllib.parse import urljoin

import html2text
import httpx
from autogen_core.code_executor import ImportFromModule
from autogen_core.tools import FunctionTool
from bs4 import BeautifulSoup


async def bing_search(
    query: str,
    num_results: int = 3,
    include_snippets: bool = True,
    include_content: bool = True,
    content_max_length: Optional[int] = 10000,
    language: str = "en",
    country: Optional[str] = None,
    safe_search: str = "moderate",
    response_filter: str = "webpages",
) -> List[Dict[str, str]]:
    """
    Perform a Bing search using the Bing Web Search API.

    Args:
        query: Search query string
        num_results: Number of results to return (max 50)
        include_snippets: Include result snippets in output
        include_content: Include full webpage content in markdown format
        content_max_length: Maximum length of webpage content (if included)
        language: Language code for search results (e.g., 'en', 'es', 'fr')
        country: Optional market code for search results (e.g., 'us', 'uk')
        safe_search: SafeSearch setting ('off', 'moderate', or 'strict')
        response_filter: Type of results ('webpages', 'news', 'images', or 'videos')

    Returns:
        List[Dict[str, str]]: List of search results

    Raises:
        ValueError: If API credentials are invalid or request fails
    """
    # Get and validate API key
    api_key = os.getenv("BING_SEARCH_KEY", "").strip()

    if not api_key:
        raise ValueError(
            "BING_SEARCH_KEY environment variable is not set. " "Please obtain an API key from Azure Portal."
        )

    # Validate safe_search parameter
    valid_safe_search = ["off", "moderate", "strict"]
    if safe_search.lower() not in valid_safe_search:
        raise ValueError(f"Invalid safe_search value. Must be one of: {', '.join(valid_safe_search)}")

    # Validate response_filter parameter
    valid_filters = ["webpages", "news", "images", "videos"]
    if response_filter.lower() not in valid_filters:
        raise ValueError(f"Invalid response_filter value. Must be one of: {', '.join(valid_filters)}")

    async def fetch_page_content(url: str, max_length: Optional[int] = 50000) -> str:
        """Helper function to fetch and convert webpage content to markdown"""
        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}

        try:
            async with httpx.AsyncClient() as client:
                response = await client.get(url, headers=headers, timeout=10)
                response.raise_for_status()

                soup = BeautifulSoup(response.text, "html.parser")

                # Remove script and style elements
                for script in soup(["script", "style"]):
                    script.decompose()

                # Convert relative URLs to absolute
                for tag in soup.find_all(["a", "img"]):
                    if tag.get("href"):
                        tag["href"] = urljoin(url, tag["href"])
                    if tag.get("src"):
                        tag["src"] = urljoin(url, tag["src"])

                h2t = html2text.HTML2Text()
                h2t.body_width = 0
                h2t.ignore_images = False
                h2t.ignore_emphasis = False
                h2t.ignore_links = False
                h2t.ignore_tables = False

                markdown = h2t.handle(str(soup))

                if max_length and len(markdown) > max_length:
                    markdown = markdown[:max_length] + "\n...(truncated)"

                return markdown.strip()

        except Exception as e:
            return f"Error fetching content: {str(e)}"

    # Build request headers and parameters
    headers = {"Ocp-Apim-Subscription-Key": api_key, "Accept": "application/json"}

    params = {
        "q": query,
        "count": min(max(1, num_results), 50),
        "mkt": f"{language}-{country.upper()}" if country else language,
        "safeSearch": safe_search.capitalize(),
        "responseFilter": response_filter,
        "textFormat": "raw",
    }

    # Make the request
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://api.bing.microsoft.com/v7.0/search", headers=headers, params=params, timeout=10
            )

            # Handle common error cases
            if response.status_code == 401:
                raise ValueError("Authentication failed. Please verify your Bing Search API key.")
            elif response.status_code == 403:
                raise ValueError(
                    "Access forbidden. This could mean:\n"
                    "1. The API key is invalid\n"
                    "2. The API key has expired\n"
                    "3. You've exceeded your API quota"
                )
            elif response.status_code == 429:
                raise ValueError("API quota exceeded. Please try again later.")

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

        # Process results based on response_filter
        results = []
        if response_filter == "webpages" and "webPages" in data:
            items = data["webPages"]["value"]
        elif response_filter == "news" and "news" in data:
            items = data["news"]["value"]
        elif response_filter == "images" and "images" in data:
            items = data["images"]["value"]
        elif response_filter == "videos" and "videos" in data:
            items = data["videos"]["value"]
        else:
            if not any(key in data for key in ["webPages", "news", "images", "videos"]):
                return []  # No results found
            raise ValueError(f"No {response_filter} results found in API response")

        # Extract relevant information based on result type
        for item in items:
            result = {"title": item.get("name", "")}

            if response_filter == "webpages":
                result["link"] = item.get("url", "")
                if include_snippets:
                    result["snippet"] = item.get("snippet", "")
                if include_content:
                    result["content"] = await fetch_page_content(result["link"], max_length=content_max_length)

            elif response_filter == "news":
                result["link"] = item.get("url", "")
                if include_snippets:
                    result["snippet"] = item.get("description", "")
                result["date"] = item.get("datePublished", "")
                if include_content:
                    result["content"] = await fetch_page_content(result["link"], max_length=content_max_length)

            elif response_filter == "images":
                result["link"] = item.get("contentUrl", "")
                result["thumbnail"] = item.get("thumbnailUrl", "")
                if include_snippets:
                    result["snippet"] = item.get("description", "")

            elif response_filter == "videos":
                result["link"] = item.get("contentUrl", "")
                result["thumbnail"] = item.get("thumbnailUrl", "")
                if include_snippets:
                    result["snippet"] = item.get("description", "")
                result["duration"] = item.get("duration", "")

            results.append(result)

        return results[:num_results]

    except httpx.HTTPError as e:
        error_msg = str(e)
        if "InvalidApiKey" in error_msg:
            raise ValueError("Invalid API key. Please check your BING_SEARCH_KEY environment variable.") from e
        elif "KeyExpired" in error_msg:
            raise ValueError("API key has expired. Please generate a new key.") from e
        else:
            raise ValueError(f"Search request failed: {error_msg}") from e
    except json.JSONDecodeError:
        raise ValueError("Failed to parse API response. " "Please verify your API credentials and try again.") from None
    except Exception as e:
        raise ValueError(f"Unexpected error during search: {str(e)}") from e


# Create the Bing search tool
bing_search_tool = FunctionTool(
    func=bing_search,
    description="""
    Perform Bing searches using the Bing Web Search API. Requires BING_SEARCH_KEY environment variable.
    Supports web, news, image, and video searches.
    See function documentation for detailed setup instructions.
    """,
    global_imports=[
        ImportFromModule("typing", ("List", "Dict", "Optional")),
        "os",
        "httpx",
        "json",
        "html2text",
        ImportFromModule("bs4", ("BeautifulSoup",)),
        ImportFromModule("urllib.parse", ("urljoin",)),
    ],
)
