"""
WebFetchTool - Fetch content from URLs

Following OpenCode architecture, provides web content retrieval with:
- URL fetching with configurable timeouts
- HTML to Markdown conversion
- Redirect handling
- Cache support
- Type-safe implementation
"""

import re
from dataclasses import dataclass
from datetime import datetime, timedelta
from html.parser import HTMLParser
from typing import Any, Optional
from urllib.parse import urlparse

try:
    import requests

    REQUESTS_AVAILABLE = True
except ImportError:
    REQUESTS_AVAILABLE = False

from groknroll.tools.base_tool import BaseTool


class HTMLToMarkdown(HTMLParser):
    """Simple HTML to Markdown converter"""

    def __init__(self):
        super().__init__()
        self.result = []
        self.in_pre = False
        self.in_code = False
        self.in_link = False
        self.link_href = ""
        self.link_text = ""
        self.list_depth = 0
        self.ignore_content = False

    def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]):
        attrs_dict = dict(attrs)

        if tag in ("script", "style", "noscript"):
            self.ignore_content = True
        elif tag == "h1":
            self.result.append("\n# ")
        elif tag == "h2":
            self.result.append("\n## ")
        elif tag == "h3":
            self.result.append("\n### ")
        elif tag == "h4":
            self.result.append("\n#### ")
        elif tag == "p":
            self.result.append("\n\n")
        elif tag == "br":
            self.result.append("\n")
        elif tag == "strong" or tag == "b":
            self.result.append("**")
        elif tag == "em" or tag == "i":
            self.result.append("*")
        elif tag == "code":
            self.in_code = True
            if not self.in_pre:
                self.result.append("`")
        elif tag == "pre":
            self.in_pre = True
            self.result.append("\n```\n")
        elif tag == "a":
            self.in_link = True
            self.link_href = attrs_dict.get("href", "")
            self.link_text = ""
        elif tag == "ul":
            self.list_depth += 1
            self.result.append("\n")
        elif tag == "ol":
            self.list_depth += 1
            self.result.append("\n")
        elif tag == "li":
            indent = "  " * (self.list_depth - 1)
            self.result.append(f"{indent}- ")
        elif tag == "blockquote":
            self.result.append("\n> ")
        elif tag == "img":
            alt = attrs_dict.get("alt", "image")
            src = attrs_dict.get("src", "")
            self.result.append(f"![{alt}]({src})")
        elif tag == "hr":
            self.result.append("\n---\n")

    def handle_endtag(self, tag: str):
        if tag in ("script", "style", "noscript"):
            self.ignore_content = False
        elif tag in ("h1", "h2", "h3", "h4"):
            self.result.append("\n")
        elif tag == "p":
            self.result.append("\n")
        elif tag == "strong" or tag == "b":
            self.result.append("**")
        elif tag == "em" or tag == "i":
            self.result.append("*")
        elif tag == "code":
            self.in_code = False
            if not self.in_pre:
                self.result.append("`")
        elif tag == "pre":
            self.in_pre = False
            self.result.append("\n```\n")
        elif tag == "a":
            self.in_link = False
            if self.link_href:
                self.result.append(f"[{self.link_text}]({self.link_href})")
            else:
                self.result.append(self.link_text)
        elif tag in ("ul", "ol"):
            self.list_depth = max(0, self.list_depth - 1)
        elif tag == "li":
            self.result.append("\n")

    def handle_data(self, data: str):
        if self.ignore_content:
            return

        if self.in_link:
            self.link_text += data
        else:
            # Clean up whitespace unless in pre/code
            if not self.in_pre and not self.in_code:
                data = re.sub(r"\s+", " ", data)
            self.result.append(data)

    def get_markdown(self) -> str:
        """Get the converted markdown"""
        text = "".join(self.result)
        # Clean up excessive newlines
        text = re.sub(r"\n{3,}", "\n\n", text)
        return text.strip()


@dataclass
class FetchResult:
    """Result of a web fetch operation"""

    success: bool
    url: str
    status_code: int
    content_type: str
    content: str
    markdown: Optional[str] = None
    title: Optional[str] = None
    redirect_url: Optional[str] = None
    error: Optional[str] = None
    cached: bool = False
    fetch_time: float = 0.0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "success": self.success,
            "url": self.url,
            "status_code": self.status_code,
            "content_type": self.content_type,
            "content": self.content,
            "markdown": self.markdown,
            "title": self.title,
            "redirect_url": self.redirect_url,
            "error": self.error,
            "cached": self.cached,
            "fetch_time": self.fetch_time,
        }


@dataclass
class CacheEntry:
    """Cache entry for fetched content"""

    result: FetchResult
    fetched_at: datetime
    ttl: timedelta


class WebFetchTool(BaseTool):
    """
    Tool for fetching web content

    Accepts:
        url: str - The URL to fetch
        prompt: str (optional) - Prompt to process content with
        timeout: int (optional) - Request timeout in seconds (default: 30)
        convert_to_markdown: bool (optional) - Convert HTML to Markdown (default: True)
        cache_ttl: int (optional) - Cache TTL in seconds (default: 900)

    Returns:
        FetchResult - Fetched content

    Raises:
        ValueError: If URL is invalid
        ConnectionError: If fetch fails

    Example:
        tool = WebFetchTool()

        # Fetch URL
        result = await tool.execute(url="https://example.com")
        print(result.markdown)

        # With processing prompt
        result = await tool.execute(
            url="https://docs.python.org/3/",
            prompt="Extract the main navigation links"
        )
    """

    def __init__(self):
        """Initialize WebFetchTool"""
        self._cache: dict[str, CacheEntry] = {}
        self._default_timeout = 30
        self._default_cache_ttl = 900  # 15 minutes
        self._max_content_size = 10 * 1024 * 1024  # 10MB

    @property
    def name(self) -> str:
        """Tool identifier"""
        return "webfetch"

    @property
    def description(self) -> str:
        """Human-readable description"""
        return "Fetch content from a URL and optionally convert to Markdown"

    def validate_params(self, **kwargs) -> dict[str, Any]:
        """
        Validate parameters before execution

        Args:
            **kwargs: Must contain 'url' parameter

        Returns:
            Validated parameters dict

        Raises:
            ValueError: If parameters are invalid
        """
        if "url" not in kwargs:
            raise ValueError("url parameter is required")

        url = kwargs["url"]
        if not isinstance(url, str):
            raise ValueError(f"url must be a string, got {type(url).__name__}")

        if not url.strip():
            raise ValueError("url cannot be empty")

        # Validate URL format
        parsed = urlparse(url)
        if not parsed.scheme:
            # Default to https
            url = "https://" + url
            parsed = urlparse(url)

        if parsed.scheme not in ("http", "https"):
            raise ValueError(f"URL scheme must be http or https, got {parsed.scheme}")

        if not parsed.netloc:
            raise ValueError("Invalid URL: no host specified")

        kwargs["url"] = url

        # Validate timeout
        timeout = kwargs.get("timeout", self._default_timeout)
        if not isinstance(timeout, (int, float)):
            raise ValueError(f"timeout must be a number, got {type(timeout).__name__}")
        if timeout <= 0:
            raise ValueError(f"timeout must be positive, got {timeout}")

        return kwargs

    async def execute(self, **kwargs) -> FetchResult:
        """
        Execute web fetch operation

        Args:
            **kwargs: Fetch parameters

        Returns:
            FetchResult with fetched content
        """
        if not REQUESTS_AVAILABLE:
            return FetchResult(
                success=False,
                url=kwargs.get("url", ""),
                status_code=0,
                content_type="",
                content="",
                error="requests library not available. Install with: pip install requests",
            )

        # Validate parameters
        validated = self.validate_params(**kwargs)
        url = validated["url"]
        timeout = validated.get("timeout", self._default_timeout)
        convert_to_markdown = validated.get("convert_to_markdown", True)
        cache_ttl = validated.get("cache_ttl", self._default_cache_ttl)

        # Check cache
        cached_result = self._get_cached(url)
        if cached_result:
            cached_result.cached = True
            return cached_result

        # Fetch URL
        import time

        start_time = time.time()

        try:
            response = requests.get(
                url,
                timeout=timeout,
                headers={
                    "User-Agent": "groknroll-webfetch/1.0",
                    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                },
                allow_redirects=True,
            )

            fetch_time = time.time() - start_time

            # Check for redirect to different host
            if response.history:
                original_host = urlparse(url).netloc
                final_host = urlparse(response.url).netloc
                if original_host != final_host:
                    return FetchResult(
                        success=False,
                        url=url,
                        status_code=response.status_code,
                        content_type=response.headers.get("Content-Type", ""),
                        content="",
                        redirect_url=response.url,
                        error=f"Redirected to different host: {response.url}",
                        fetch_time=fetch_time,
                    )

            # Check content size
            content_length = response.headers.get("Content-Length")
            if content_length and int(content_length) > self._max_content_size:
                return FetchResult(
                    success=False,
                    url=url,
                    status_code=response.status_code,
                    content_type=response.headers.get("Content-Type", ""),
                    content="",
                    error=f"Content too large: {content_length} bytes",
                    fetch_time=fetch_time,
                )

            # Get content
            content = response.text[: self._max_content_size]
            content_type = response.headers.get("Content-Type", "")

            # Convert HTML to Markdown if requested
            markdown = None
            title = None
            if convert_to_markdown and "text/html" in content_type:
                markdown, title = self._html_to_markdown(content)

            result = FetchResult(
                success=response.ok,
                url=response.url,
                status_code=response.status_code,
                content_type=content_type,
                content=content,
                markdown=markdown,
                title=title,
                fetch_time=fetch_time,
            )

            # Cache successful results
            if result.success:
                self._set_cached(url, result, cache_ttl)

            return result

        except requests.Timeout:
            return FetchResult(
                success=False,
                url=url,
                status_code=0,
                content_type="",
                content="",
                error=f"Request timed out after {timeout} seconds",
                fetch_time=time.time() - start_time,
            )
        except requests.ConnectionError as e:
            return FetchResult(
                success=False,
                url=url,
                status_code=0,
                content_type="",
                content="",
                error=f"Connection error: {e}",
                fetch_time=time.time() - start_time,
            )
        except Exception as e:
            return FetchResult(
                success=False,
                url=url,
                status_code=0,
                content_type="",
                content="",
                error=f"Fetch failed: {e}",
                fetch_time=time.time() - start_time,
            )

    def _html_to_markdown(self, html: str) -> tuple[str, Optional[str]]:
        """
        Convert HTML to Markdown

        Args:
            html: HTML content

        Returns:
            Tuple of (markdown, title)
        """
        # Extract title
        title_match = re.search(r"<title[^>]*>([^<]+)</title>", html, re.IGNORECASE)
        title = title_match.group(1).strip() if title_match else None

        # Convert to markdown
        parser = HTMLToMarkdown()
        try:
            parser.feed(html)
            markdown = parser.get_markdown()
        except Exception:
            # If parsing fails, return raw text
            markdown = re.sub(r"<[^>]+>", "", html)
            markdown = re.sub(r"\s+", " ", markdown)

        return markdown, title

    def _get_cached(self, url: str) -> Optional[FetchResult]:
        """Get cached result if valid"""
        if url in self._cache:
            entry = self._cache[url]
            if datetime.now() < entry.fetched_at + entry.ttl:
                return entry.result
            else:
                # Expired - remove from cache
                del self._cache[url]
        return None

    def _set_cached(self, url: str, result: FetchResult, ttl: int) -> None:
        """Cache a result"""
        self._cache[url] = CacheEntry(
            result=result,
            fetched_at=datetime.now(),
            ttl=timedelta(seconds=ttl),
        )

    def clear_cache(self) -> int:
        """
        Clear the cache

        Returns:
            Number of entries cleared
        """
        count = len(self._cache)
        self._cache.clear()
        return count
