"""Web fetch tool for Promethian.

Downloads content from URLs - handles both text and binary files.
"""

from __future__ import annotations

import asyncio
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import httpx


@dataclass
class FetchResult:
    """Result from fetching a URL."""
    success: bool
    url: str
    content: str | None = None  # Text content (for HTML, text, JSON, etc.)
    file_path: str | None = None  # Path to downloaded file (for binary)
    content_type: str | None = None
    status_code: int | None = None
    error: str | None = None
    is_binary: bool = False


# Content types that should be returned as text
TEXT_CONTENT_TYPES = {
    "text/html",
    "text/plain",
    "text/css",
    "text/javascript",
    "text/csv",
    "text/xml",
    "application/json",
    "application/xml",
    "application/javascript",
    "application/x-javascript",
}

# Extensions that are typically text
TEXT_EXTENSIONS = {
    ".html", ".htm", ".txt", ".css", ".js", ".json", ".xml",
    ".csv", ".tsv", ".md", ".yaml", ".yml", ".toml", ".ini",
    ".py", ".rb", ".java", ".c", ".cpp", ".h", ".rs", ".go",
}


class WebFetchTool:
    """
    Tool for fetching content from URLs.

    Handles:
    - Text content (HTML, JSON, CSV, etc.) - returned directly
    - Binary files (PDF, images, etc.) - saved to temp file, path returned
    """

    # Max text content for LLM context (~50k tokens at 4 chars/token)
    DEFAULT_MAX_TEXT_SIZE = 200 * 1024  # 200KB
    # Max binary file size for downloads
    DEFAULT_MAX_BINARY_SIZE = 10 * 1024 * 1024  # 10MB

    def __init__(
        self,
        timeout: int = 30,
        max_text_size: int | None = None,
        max_binary_size: int | None = None,
        download_dir: Path | None = None,
    ):
        self.timeout = timeout
        self.max_text_size = max_text_size or self.DEFAULT_MAX_TEXT_SIZE
        self.max_binary_size = max_binary_size or self.DEFAULT_MAX_BINARY_SIZE
        self.download_dir = download_dir or Path(tempfile.gettempdir()) / "promethian_downloads"
        self.download_dir.mkdir(parents=True, exist_ok=True)

    def _is_text_content(self, content_type: str | None, url: str) -> bool:
        """Determine if content should be treated as text."""
        if content_type:
            # Check content type
            ct_lower = content_type.lower().split(";")[0].strip()
            if ct_lower in TEXT_CONTENT_TYPES:
                return True
            if ct_lower.startswith("text/"):
                return True

        # Check URL extension
        path = urlparse(url).path.lower()
        for ext in TEXT_EXTENSIONS:
            if path.endswith(ext):
                return True

        return False

    def _get_filename(self, url: str, content_type: str | None) -> str:
        """Generate a filename for downloaded content."""
        parsed = urlparse(url)
        path = parsed.path

        # Try to get filename from URL path
        if path and "/" in path:
            filename = path.split("/")[-1]
            if filename and "." in filename:
                return filename

        # Generate from domain + extension
        domain = parsed.netloc.replace(".", "_")

        # Guess extension from content type
        ext = ".bin"
        if content_type:
            ct = content_type.lower().split(";")[0].strip()
            ext_map = {
                "application/pdf": ".pdf",
                "application/zip": ".zip",
                "application/gzip": ".gz",
                "image/png": ".png",
                "image/jpeg": ".jpg",
                "image/gif": ".gif",
                "application/vnd.ms-excel": ".xls",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
                "text/csv": ".csv",
                "application/json": ".json",
            }
            ext = ext_map.get(ct, ext)

        return f"{domain}_{hash(url) % 10000}{ext}"

    async def fetch(
        self,
        url: str,
        headers: dict[str, str] | None = None,
    ) -> FetchResult:
        """
        Fetch content from a URL.

        For text content (HTML, JSON, CSV, etc.), returns the content directly.
        For binary files (PDF, images, etc.), saves to temp file and returns path.

        Args:
            url: The URL to fetch
            headers: Optional HTTP headers

        Returns:
            FetchResult with content or file path
        """
        try:
            default_headers = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            }
            if headers:
                default_headers.update(headers)

            async with httpx.AsyncClient(
                timeout=self.timeout,
                follow_redirects=True,
                headers=default_headers,
            ) as client:
                response = await client.get(url)
                response.raise_for_status()

                content_type = response.headers.get("content-type", "")
                content_length = int(response.headers.get("content-length", 0))
                is_text = self._is_text_content(content_type, url)

                # Check size limit based on content type
                max_size = self.max_text_size if is_text else self.max_binary_size
                if content_length > max_size and not is_text:
                    # Only block for binary files; text will be truncated
                    return FetchResult(
                        success=False,
                        url=url,
                        error=f"Binary content too large: {content_length} bytes (max {max_size})",
                        status_code=response.status_code,
                        content_type=content_type,
                    )

                if is_text:
                    # Return text content directly
                    content = response.text

                    # Truncate large text content for LLM context
                    if len(content) > self.max_text_size:
                        # Try to truncate at a reasonable boundary (paragraph)
                        truncate_at = self.max_text_size
                        # Look for paragraph break near truncation point
                        newline_pos = content.rfind("\n\n", truncate_at - 1000, truncate_at)
                        if newline_pos > truncate_at - 1000:
                            truncate_at = newline_pos

                        content = content[:truncate_at] + f"\n\n[Content truncated at {truncate_at:,} chars - original was {len(response.text):,} chars]"

                    return FetchResult(
                        success=True,
                        url=url,
                        content=content,
                        content_type=content_type,
                        status_code=response.status_code,
                        is_binary=False,
                    )
                else:
                    # Save binary to file
                    filename = self._get_filename(url, content_type)
                    file_path = self.download_dir / filename

                    with open(file_path, "wb") as f:
                        f.write(response.content)

                    return FetchResult(
                        success=True,
                        url=url,
                        file_path=str(file_path),
                        content_type=content_type,
                        status_code=response.status_code,
                        is_binary=True,
                    )

        except httpx.TimeoutException:
            return FetchResult(
                success=False,
                url=url,
                error=f"Request timed out after {self.timeout}s",
            )
        except httpx.HTTPStatusError as e:
            return FetchResult(
                success=False,
                url=url,
                error=f"HTTP {e.response.status_code}: {e.response.reason_phrase}",
                status_code=e.response.status_code,
            )
        except Exception as e:
            return FetchResult(
                success=False,
                url=url,
                error=str(e),
            )

    def get_tool_schemas(self) -> list[dict]:
        """Get Claude tool schemas for web fetch actions."""
        return [
            {
                "name": "web_fetch",
                "description": (
                    "Fetch content from a URL. For text content (HTML, JSON, CSV, etc.), "
                    "returns the full content directly. For binary files (PDF, images, Excel), "
                    "downloads to a temporary file and returns the file path. Use this to "
                    "download and read web pages, API responses, data files, or documents."
                ),
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "url": {
                            "type": "string",
                            "description": "The URL to fetch"
                        },
                    },
                    "required": ["url"]
                }
            },
        ]

    async def execute_tool(self, name: str, params: dict) -> FetchResult:
        """Execute a web fetch tool by name."""
        if name == "web_fetch":
            return await self.fetch(
                url=params["url"],
                headers=params.get("headers"),
            )
        else:
            return FetchResult(
                success=False,
                url=params.get("url", ""),
                error=f"Unknown web fetch tool: {name}",
            )


# Singleton instance
_web_fetch: WebFetchTool | None = None


def get_web_fetch(
    timeout: int = 30,
    max_text_size: int | None = None,
    max_binary_size: int | None = None,
) -> WebFetchTool:
    """Get the global web fetch tool instance."""
    global _web_fetch
    if _web_fetch is None:
        _web_fetch = WebFetchTool(
            timeout=timeout,
            max_text_size=max_text_size,
            max_binary_size=max_binary_size,
        )
    return _web_fetch


def reset_web_fetch() -> None:
    """Reset the global web fetch tool instance."""
    global _web_fetch
    _web_fetch = None
