"""
Structured metadata extraction using extruct.

Extracts JSON-LD, OpenGraph, and microdata from HTML pages.
Used to enrich text extraction with structured data — especially
useful for product pages, articles, and other schema.org-annotated
content where text extraction alone misses key information.
"""

from typing import Any, Dict, Optional

from loguru import logger


def extract_metadata(html: str, url: str = "") -> Dict[str, Any]:
    """Extract structured metadata from HTML.

    Pulls JSON-LD, OpenGraph, and microdata in one pass.

    Args:
        html: Raw HTML string.
        url: Base URL for resolving relative URLs in metadata.

    Returns:
        Dict with keys: json_ld, opengraph, microdata (each a list of dicts).
        Empty lists if nothing found or extruct not installed.
    """
    result: Dict[str, Any] = {
        "json_ld": [],
        "opengraph": [],
        "microdata": [],
    }

    if not html or not html.strip():
        return result

    try:
        import extruct
    except ImportError:
        logger.debug("extruct not installed — skipping metadata extraction")
        return result

    try:
        data = extruct.extract(
            html,
            base_url=url,
            syntaxes=["json-ld", "opengraph", "microdata"],
            uniform=True,
        )
        result["json_ld"] = data.get("json-ld", [])
        result["opengraph"] = data.get("opengraph", [])
        result["microdata"] = data.get("microdata", [])
    except Exception:
        logger.debug("extruct metadata extraction failed", exc_info=True)

    return result


def metadata_to_text(metadata: Dict[str, Any]) -> Optional[str]:
    """Convert structured metadata into readable text supplement.

    Extracts the most useful fields from JSON-LD, OpenGraph, and
    microdata and formats them as a text block that can be appended
    to extracted content.

    Args:
        metadata: Output from extract_metadata().

    Returns:
        Formatted text string, or None if no useful metadata found.
    """
    parts = []

    # JSON-LD — richest source (Schema.org types)
    for item in metadata.get("json_ld", []):
        item_type = item.get("@type", "")
        if isinstance(item_type, list):
            item_type = item_type[0] if item_type else ""

        if item_type == "Product":
            parts.extend(_format_product(item))
        elif item_type in (
            "Article",
            "NewsArticle",
            "BlogPosting",
            "ScholarlyArticle",
        ):
            parts.extend(_format_article(item))
        elif item_type == "SoftwareSourceCode":
            parts.extend(_format_software(item))
        elif item_type in ("Dataset", "CreativeWork"):
            parts.extend(_format_generic(item))

    # Microdata — check for types not covered by JSON-LD
    for item in metadata.get("microdata", []):
        item_type = item.get("@type", "")
        if isinstance(item_type, list):
            item_type = item_type[0] if item_type else ""

        if item_type == "Product" and not _has_type(
            metadata["json_ld"], "Product"
        ):
            parts.extend(_format_product(item))
        elif item_type == "SoftwareSourceCode" and not _has_type(
            metadata["json_ld"], "SoftwareSourceCode"
        ):
            parts.extend(_format_software(item))

    # OpenGraph — fallback when JSON-LD/microdata don't have structured types
    if not parts:
        for item in metadata.get("opengraph", []):
            og_parts = _format_opengraph(item)
            if og_parts:
                parts.extend(og_parts)
                break  # Only use the first OG block

    if not parts:
        return None

    return "\n".join(parts)


def _has_type(items: list, type_name: str) -> bool:
    """Check if any item in the list has the given @type."""
    for item in items:
        t = item.get("@type", "")
        if isinstance(t, list):
            if type_name in t:
                return True
        elif t == type_name:
            return True
    return False


def _format_product(item: dict) -> list:
    """Format Product schema into readable text."""
    parts = []
    name = item.get("name", "")
    if name:
        parts.append(f"Product: {name}")

    desc = item.get("description", "")
    if desc and len(desc) > 20:
        parts.append(f"Description: {desc[:500]}")

    brand = item.get("brand", "")
    if isinstance(brand, dict):
        brand = brand.get("name", "")
    if brand:
        parts.append(f"Brand: {brand}")

    # Price
    offers = item.get("offers", {})
    if isinstance(offers, list):
        offers = offers[0] if offers else {}
    if isinstance(offers, dict):
        price = offers.get("price", "")
        currency = offers.get("priceCurrency", "")
        if price:
            parts.append(
                f"Price: {currency} {price}" if currency else f"Price: {price}"
            )
        availability = offers.get("availability", "")
        if availability:
            parts.append(f"Availability: {availability}")

    # Rating
    rating = item.get("aggregateRating", {})
    if isinstance(rating, dict):
        value = rating.get("ratingValue", "")
        count = rating.get("reviewCount", rating.get("ratingCount", ""))
        if value:
            parts.append(f"Rating: {value}/5 ({count} reviews)")

    return parts


def _format_article(item: dict) -> list:
    """Format Article schema into readable text."""
    parts = []
    name = item.get("headline", item.get("name", ""))
    if name:
        parts.append(f"Article: {name}")

    author = item.get("author", "")
    if isinstance(author, dict):
        author = author.get("name", "")
    elif isinstance(author, list):
        author = ", ".join(
            a.get("name", str(a)) if isinstance(a, dict) else str(a)
            for a in author
        )
    if author:
        parts.append(f"Author: {author}")

    date = item.get("datePublished", "")
    if date:
        parts.append(f"Published: {date}")

    body = item.get("articleBody", "")
    if body and len(body) > 50:
        parts.append(f"Content: {body[:2000]}")

    return parts


def _format_software(item: dict) -> list:
    """Format SoftwareSourceCode schema into readable text."""
    parts = []
    name = item.get("name", "")
    if name:
        parts.append(f"Repository: {name}")

    author = item.get("author", "")
    if isinstance(author, dict):
        author = author.get("name", "")
    elif isinstance(author, list):
        author = ", ".join(
            a.get("name", str(a)) if isinstance(a, dict) else str(a)
            for a in author
        )
    if author:
        parts.append(f"Author: {author}")

    desc = item.get("description", "")
    if desc:
        parts.append(f"Description: {desc[:500]}")

    text = item.get("text", "")
    if text and len(text) > 50:
        parts.append(f"Content: {text[:2000]}")

    return parts


def _format_opengraph(item: dict) -> list:
    """Format OpenGraph metadata into readable text."""
    parts = []
    og_type = item.get("@type", item.get("og:type", ""))
    title = item.get("og:title", "")
    desc = item.get("og:description", "")
    site = item.get("og:site_name", "")

    # Filter out generic OG types that add no information as prefixes
    generic_og_types = {"website", "article", ""}
    if title:
        prefix = (
            site
            if site
            else (og_type if og_type.lower() not in generic_og_types else "")
        )
        parts.append(f"{prefix}: {title}" if prefix else title)
    if desc and len(desc) > 20:
        parts.append(f"Description: {desc[:500]}")

    # Price tags (some e-commerce sites put these in OG)
    price = item.get("product:price:amount", item.get("og:price:amount", ""))
    currency = item.get(
        "product:price:currency", item.get("og:price:currency", "")
    )
    if price:
        parts.append(f"Price: {currency} {price}")

    return parts


def _format_generic(item: dict) -> list:
    """Format a generic CreativeWork/Dataset schema."""
    parts = []
    name = item.get("name", item.get("headline", ""))
    if name:
        parts.append(f"Name: {name}")

    desc = item.get("description", "")
    if desc and len(desc) > 20:
        parts.append(f"Description: {desc[:500]}")

    return parts
