"""
HTML Content Extraction

Extracts text and structure from HTML documents and web pages.
Uses BeautifulSoup for parsing.
"""

import logging
import re
from dataclasses import dataclass, field
from pathlib import Path
from urllib.parse import urljoin, urlparse

logger = logging.getLogger(__name__)


@dataclass
class HTMLTable:
    """Represents a table extracted from HTML."""

    table_index: int
    headers: list[str]
    rows: list[list[str]]
    caption: str | None = None
    id: str | None = None

    def to_markdown(self) -> str:
        """Convert table to markdown format."""
        if not self.headers and not self.rows:
            return ""

        lines = []

        if self.caption:
            lines.append(f"**{self.caption}**\n")

        headers = self.headers or ([""] * (len(self.rows[0]) if self.rows else 0))
        if headers:
            lines.append("| " + " | ".join(headers) + " |")
            lines.append("| " + " | ".join(["---"] * len(headers)) + " |")

        for row in self.rows:
            if headers:
                padded_row = row + [""] * (len(headers) - len(row))
                lines.append("| " + " | ".join(padded_row[:len(headers)]) + " |")
            else:
                lines.append("| " + " | ".join(row) + " |")

        return "\n".join(lines)


@dataclass
class HTMLSection:
    """Represents a section in HTML content."""

    title: str
    level: int  # h1=1, h2=2, etc.
    content: str = ""
    id: str | None = None


@dataclass
class HTMLLink:
    """Represents a link extracted from HTML."""

    text: str
    href: str
    is_internal: bool = False


@dataclass
class HTMLExtractionResult:
    """Result of HTML extraction."""

    source: str  # File path or URL
    title: str | None = None
    text_content: str = ""
    sections: list[HTMLSection] = field(default_factory=list)
    tables: list[HTMLTable] = field(default_factory=list)
    links: list[HTMLLink] = field(default_factory=list)
    metadata: dict = field(default_factory=dict)

    @property
    def has_tables(self) -> bool:
        return len(self.tables) > 0


class HTMLExtractor:
    """
    Extracts content from HTML documents.

    Features:
    - Clean text extraction (removes scripts, styles, etc.)
    - Table extraction with header detection
    - Section/heading hierarchy extraction
    - Link extraction
    - Metadata extraction (title, description, etc.)

    Example:
        extractor = HTMLExtractor()
        result = extractor.extract_file("document.html")
        print(result.text_content)
        for table in result.tables:
            print(table.to_markdown())
    """

    # Tags to remove entirely
    REMOVE_TAGS = ["script", "style", "noscript", "iframe", "svg", "canvas"]

    # Tags that typically contain the main content
    CONTENT_TAGS = ["article", "main", "section", "div"]

    def __init__(
        self,
        extract_tables: bool = True,
        extract_links: bool = True,
        extract_structure: bool = True,
        base_url: str | None = None,
    ):
        """
        Initialize HTML extractor.

        Args:
            extract_tables: Whether to extract tables
            extract_links: Whether to extract links
            extract_structure: Whether to extract document structure
            base_url: Base URL for resolving relative links
        """
        self.extract_tables = extract_tables
        self.extract_links = extract_links
        self.extract_structure = extract_structure
        self.base_url = base_url
        self._check_dependencies()

    def _check_dependencies(self):
        """Check that required dependencies are available."""
        try:
            from bs4 import BeautifulSoup  # noqa: F401
        except ImportError:
            raise ImportError("beautifulsoup4 not installed. Run: pip install beautifulsoup4") from None

    def extract_file(self, file_path: str | Path) -> HTMLExtractionResult:
        """
        Extract content from an HTML file.

        Args:
            file_path: Path to HTML file

        Returns:
            HTMLExtractionResult containing extracted content
        """
        file_path = Path(file_path)

        if not file_path.exists():
            raise FileNotFoundError(f"HTML file not found: {file_path}")

        with open(file_path, encoding="utf-8", errors="ignore") as f:
            html_content = f.read()

        return self.extract_html(html_content, source=str(file_path))

    def extract_html(
        self,
        html_content: str,
        source: str = "unknown",
    ) -> HTMLExtractionResult:
        """
        Extract content from HTML string.

        Args:
            html_content: HTML content as string
            source: Source identifier (file path or URL)

        Returns:
            HTMLExtractionResult containing extracted content
        """
        from bs4 import BeautifulSoup

        soup = BeautifulSoup(html_content, "html.parser")

        # Remove unwanted tags
        for tag in self.REMOVE_TAGS:
            for element in soup.find_all(tag):
                element.decompose()

        # Extract metadata
        metadata = self._extract_metadata(soup)
        title = metadata.get("title")

        # Extract text content
        text_content = self._extract_text(soup)

        # Extract structure (headings)
        sections = []
        if self.extract_structure:
            sections = self._extract_sections(soup)

        # Extract tables
        tables = []
        if self.extract_tables:
            tables = self._extract_tables(soup)

        # Extract links
        links = []
        if self.extract_links:
            links = self._extract_links(soup, source)

        return HTMLExtractionResult(
            source=source,
            title=title,
            text_content=text_content,
            sections=sections,
            tables=tables,
            links=links,
            metadata=metadata,
        )

    def _extract_metadata(self, soup) -> dict:
        """Extract metadata from HTML head."""
        metadata = {}

        # Title
        title_tag = soup.find("title")
        if title_tag:
            metadata["title"] = title_tag.get_text(strip=True)

        # Meta tags
        for meta in soup.find_all("meta"):
            name = meta.get("name", "").lower()
            property_ = meta.get("property", "").lower()
            content = meta.get("content", "")

            if name == "description":
                metadata["description"] = content
            elif name == "keywords":
                metadata["keywords"] = content
            elif name == "author":
                metadata["author"] = content
            elif property_ == "og:title":
                metadata["og_title"] = content
            elif property_ == "og:description":
                metadata["og_description"] = content

        return metadata

    def _extract_text(self, soup) -> str:
        """Extract clean text content."""
        # Try to find main content area
        main_content = None
        for tag in ["article", "main", "[role='main']"]:
            main_content = soup.select_one(tag)
            if main_content:
                break

        if main_content is None:
            main_content = soup.body or soup

        # Get text with some structure preservation
        text_parts = []

        for element in main_content.descendants:
            if element.name in ["p", "div", "section", "article"]:
                text = element.get_text(separator=" ", strip=True)
                if text and len(text) > 10:  # Skip very short fragments
                    text_parts.append(text)
            elif element.name in ["h1", "h2", "h3", "h4", "h5", "h6"]:
                level = int(element.name[1])
                text = element.get_text(strip=True)
                if text:
                    text_parts.append(f"\n{'#' * level} {text}\n")
            elif element.name == "li":
                text = element.get_text(strip=True)
                if text:
                    text_parts.append(f"- {text}")
            elif element.name == "br":
                text_parts.append("\n")

        # Fall back to simple text extraction if we got nothing
        if not text_parts:
            text = main_content.get_text(separator="\n", strip=True)
            return self._clean_text(text)

        return self._clean_text("\n".join(text_parts))

    def _clean_text(self, text: str) -> str:
        """Clean extracted text."""
        # Normalize whitespace
        text = re.sub(r"[ \t]+", " ", text)
        text = re.sub(r"\n{3,}", "\n\n", text)

        # Remove empty lines with only whitespace
        lines = [line.strip() for line in text.split("\n")]
        text = "\n".join(lines)

        return text.strip()

    def _extract_sections(self, soup) -> list[HTMLSection]:
        """Extract headings and their content."""
        sections = []

        for level in range(1, 7):
            for heading in soup.find_all(f"h{level}"):
                text = heading.get_text(strip=True)
                if text:
                    sections.append(HTMLSection(
                        title=text,
                        level=level,
                        id=heading.get("id"),
                    ))

        # Sort by document order (approximate using position in list)
        return sections

    def _extract_tables(self, soup) -> list[HTMLTable]:
        """Extract tables from HTML."""
        tables = []

        for idx, table in enumerate(soup.find_all("table")):
            # Get caption
            caption = None
            caption_tag = table.find("caption")
            if caption_tag:
                caption = caption_tag.get_text(strip=True)

            # Get headers
            headers = []
            thead = table.find("thead")
            if thead:
                for th in thead.find_all("th"):
                    headers.append(th.get_text(strip=True))
            else:
                # Try first row
                first_row = table.find("tr")
                if first_row:
                    for cell in first_row.find_all(["th", "td"]):
                        if cell.name == "th":
                            headers.append(cell.get_text(strip=True))

            # Get rows
            rows = []
            tbody = table.find("tbody") or table

            for tr in tbody.find_all("tr"):
                # Skip header row if we already got headers
                if headers and tr.find("th") and not tr.find("td"):
                    continue

                row_data = []
                for cell in tr.find_all(["td", "th"]):
                    row_data.append(cell.get_text(strip=True))

                if row_data:
                    rows.append(row_data)

            tables.append(HTMLTable(
                table_index=idx,
                headers=headers,
                rows=rows,
                caption=caption,
                id=table.get("id"),
            ))

        logger.info(f"Extracted {len(tables)} tables")
        return tables

    def _extract_links(self, soup, source: str) -> list[HTMLLink]:
        """Extract links from HTML."""
        links = []
        base_domain = None

        if self.base_url:
            parsed = urlparse(self.base_url)
            base_domain = parsed.netloc

        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            text = a.get_text(strip=True)

            if not href or href.startswith("#") or href.startswith("javascript:"):
                continue

            # Resolve relative URLs
            if self.base_url and not href.startswith(("http://", "https://")):
                href = urljoin(self.base_url, href)

            # Determine if internal
            is_internal = False
            if base_domain:
                parsed_href = urlparse(href)
                is_internal = parsed_href.netloc == base_domain

            links.append(HTMLLink(
                text=text,
                href=href,
                is_internal=is_internal,
            ))

        return links


def extract_html(
    source: str | Path,
    **kwargs,
) -> HTMLExtractionResult:
    """
    Convenience function to extract content from HTML.

    Args:
        source: HTML file path or HTML string
        **kwargs: Arguments passed to HTMLExtractor

    Returns:
        HTMLExtractionResult
    """
    extractor = HTMLExtractor(**kwargs)

    source_path = Path(source) if isinstance(source, str) else source

    if source_path.exists():
        return extractor.extract_file(source_path)
    else:
        # Assume it's HTML content
        return extractor.extract_html(str(source))
