"""Parser for acsearch.info HTML/JSON responses.

The site embeds search results as JSON in a script tag:
    acsearch.initSearchResults = [{...}, {...}, ...];

Lot detail pages embed a single result as an HTML div.auction-lot element
with data-id, .lot-img, .lot-date, .lot-hammer, .lot-desc.
"""

import json
import re
from typing import Any

from bs4 import BeautifulSoup

from .models import (
    AuctionInfo,
    CoinSpecs,
    ImageInfo,
    LotDetail,
    LotReference,
    PriceInfo,
    ProvenanceEntry,
    SearchResponse,
    SearchResult,
)


def parse_search_results(html: str, query: str = "", page: int = 1) -> SearchResponse:
    """Parse the search results page HTML into a SearchResponse.

    Extracts acsearch.initSearchResults JSON from a <script> tag.
    """
    results: list[SearchResult] = []
    total_results = 0
    time_seconds = 0.0

    # Extract acsearch.initSearchResults JSON
    match = re.search(
        r"acsearch\.initSearchResults\s*=\s*(\[[\s\S]*?\]);", html
    )
    if match:
        try:
            raw_data = json.loads(match.group(1))
            for item in raw_data:
                result = SearchResult(
                    id=str(item.get("id", "")),
                    title=item.get("title", ""),
                    description=item.get("description", ""),
                    images=[
                        ImageInfo(url=item["image"], thumbnail=item["image"])
                    ] if item.get("image") else [],
                    auction=AuctionInfo(
                        date=item.get("date", ""),
                    ),
                    price=PriceInfo(
                        realized=item.get("price", ""),
                    ),
                    url=f"https://www.acsearch.info/search.html?id={item.get('id', '')}",
                )
                results.append(result)
        except (json.JSONDecodeError, KeyError) as e:
            raise ValueError(f"Failed to parse search results JSON: {e}")
    else:
        # Fallback: parse auction-lot divs from HTML
        soup = BeautifulSoup(html, "lxml")
        for lot_div in soup.select(".auction-lot"):
            lot_id = lot_div.get("data-id", "")
            img = lot_div.select_one(".lot-img img")
            date_span = lot_div.select_one(".lot-date span")
            hammer_span = lot_div.select_one(".lot-hammer span")
            desc_b = lot_div.select_one(".lot-desc b")
            desc_span = lot_div.select_one(".lot-desc span")

            results.append(SearchResult(
                id=str(lot_id),
                title=desc_b.get_text(strip=True) if desc_b else "",
                description=desc_span.get_text(strip=True) if desc_span else "",
                images=[ImageInfo(
                    url=img["src"],
                    thumbnail=img["src"],
                )] if img and img.get("src") else [],
                auction=AuctionInfo(
                    date=date_span.get_text(strip=True) if date_span else "",
                ),
                price=PriceInfo(
                    realized=hammer_span.get_text(strip=True) if hammer_span else "",
                ),
                url=f"https://www.acsearch.info/search.html?id={lot_id}",
            ))

    # Extract total count from the heading
    # Raw: "<b>13&#039;521</b>" following "of" in the results heading
    count_match = re.search(
        r"of\s*\n\s*<b>([\d]+(?:&#039;)?[\d]*)</b>",
        html
    )
    if count_match:
        raw = count_match.group(1)
        import html as _html_
        raw = _html_.unescape(raw).replace("'", "")
        total_results = int(raw)

    # Extract timing: "(0.03 seconds)"
    time_match = re.search(r"\(([\d.]+)\s+seconds?\)", html)
    if time_match:
        time_seconds = float(time_match.group(1))

    return SearchResponse(
        query=query,
        total_results=total_results,
        page=page,
        results_per_page=100,
        time_seconds=time_seconds,
        results=results,
    )


def parse_lot_detail(html: str) -> LotDetail | None:
    """Parse a lot detail page HTML into a LotDetail.

    Uses acsearch.initSearchResults JSON (embedded in a <script> tag)
    which is the most reliable data source. Falls back to HTML parsing.
    """
    # Primary: extract from initSearchResults JSON
    match = re.search(
        r"acsearch\.initSearchResults\s*=\s*(\[[\s\S]*?\]);", html
    )
    if match:
        try:
            data = json.loads(match.group(1))
            if data:
                item = data[0]
                lot_id = str(item.get("id", ""))
                title = item.get("title", "")
                description = item.get("description", "")
                date = item.get("date", "")
                price_text = item.get("price", "")
                image_url = item.get("image", "")
                link_url = item.get("link", "")

                images = [ImageInfo(url=image_url, thumbnail=image_url)] if image_url else []
                specs = _parse_specs_from_text(description)
                references = _parse_references_from_text(description)
                provenance = _parse_provenance_from_text(description)

                return LotDetail(
                    id=lot_id,
                    title=title,
                    description=description,
                    auction=AuctionInfo(date=date),
                    price=PriceInfo(realized=price_text),
                    specs=specs,
                    images=images,
                    references=references,
                    provenance=provenance,
                    notes=f"Full lot page: {link_url}" if link_url else "",
                    url=f"https://www.acsearch.info/search.html?id={lot_id}",
                )
        except (json.JSONDecodeError, KeyError, IndexError):
            pass

    # Fallback: parse auction-lot HTML divs
    soup = BeautifulSoup(html, "lxml")
    lot_div = soup.select_one(".auction-lot.selected, .auction-lot[data-id]")
    if not lot_div:
        return None

    lot_id = str(lot_div.get("data-id", ""))
    images: list[ImageInfo] = []
    for img in lot_div.select(".lot-img img"):
        src = img.get("src", "")
        if src:
            images.append(ImageInfo(url=src, thumbnail=src))

    date_span = lot_div.select_one(".lot-date span")
    date = date_span.get_text(strip=True) if date_span else ""

    hammer_span = lot_div.select_one(".lot-hammer span")
    price_text = hammer_span.get_text(strip=True) if hammer_span else ""

    title_b = lot_div.select_one(".lot-desc b")
    title = title_b.get_text(strip=True) if title_b else ""

    desc_span = lot_div.select_one(".lot-desc span")
    description = desc_span.get_text(strip=True) if desc_span else ""

    # Parse metadata from the full page text
    full_text = soup.get_text()
    specs = _parse_specs_from_text(full_text)
    references = _parse_references_from_text(full_text)
    provenance = _parse_provenance_from_text(full_text)

    return LotDetail(
        id=lot_id,
        title=title,
        description=description,
        auction=AuctionInfo(date=date),
        price=PriceInfo(realized=price_text),
        specs=specs,
        images=images,
        references=references,
        provenance=provenance,
        url=f"https://www.acsearch.info/search.html?id={lot_id}",
    )


def _parse_specs_from_text(text: str) -> CoinSpecs:
    """Extract coin specifications from description text."""
    specs = CoinSpecs()

    # Weight: "14.26g", "14.26 g", "14.33 gm"
    weight_match = re.search(r"(\d+\.?\d*)\s*gm?\b", text)
    if weight_match:
        specs.weight = weight_match.group(0)

    # Diameter: "25 mm" or "25mm"
    diam_match = re.search(r"(\d+\.?\d*)\s*mm\b", text)
    if diam_match:
        specs.diameter = diam_match.group(0)

    # Die axis: "12h" or "12 h"
    axis_match = re.search(r"(\d+)\s*h\b", text)
    if axis_match:
        specs.die_axis = axis_match.group(0)

    # Metal: AR, AV, AE, etc.
    metal_match = re.search(r"\b(AR|AV|AE|BI|EL|PB)\b", text)
    if metal_match:
        specs.metal = metal_match.group(1)

    # Denomination
    for denom in ["Tetradrachm", "Drachm", "Didrachm", "Stater", "Denarius",
                   "Aureus", "Antoninianus", "Sestertius", "Obol", "Triobol",
                   "Hemidrachm", "Octodrachm", "Dekadrachm", "Cistophorus",
                   "Solidus", "Tremissis", "Semis"]:
        if re.search(rf"\b{denom}\b", text, re.IGNORECASE):
            specs.denomination = denom
            break

    return specs


def _parse_references_from_text(text: str) -> list[LotReference]:
    """Extract bibliographic references from description text."""
    refs: list[LotReference] = []

    # Common reference patterns
    ref_patterns = [
        (r"Le\s*Rider[-\s]*(\S+\d+\S*)", "Le Rider"),
        (r"SNG\s+(?:ANS[-\s]*)?(\S+)", "SNG"),
        (r"HGC[-\s]*(\S+)", "HGC"),
        (r"BMC[-\s]*(\S+)", "BMC"),
        (r"RPC[-\s]*(\S+)", "RPC"),
        (r"RIC[-\s]*(\S+)", "RIC"),
        (r"RSC[-\s]*(\S+)", "RSC"),
        (r"Price[-\s]*(\S+)", "Price"),
        (r"Müller[-\s]*(\S+)", "Müller"),
        (r"Thompson[-\s]*(\S+)", "Thompson"),
        (r"SNG\s+Copenhagen[-\s]*(\S+)", "SNG Copenhagen"),
        (r"SNG\s+von\s+Aulock[-\s]*(\S+)", "SNG von Aulock"),
        (r"SNG\s+Berry[-\s]*(\S+)", "SNG Berry"),
        (r"SNG\s+Ashmolean[-\s]*(\S+)", "SNG Ashmolean"),
        (r"SNG\s+Lockett[-\s]*(\S+)", "SNG Lockett"),
        (r"SNG\s+Delepierre[-\s]*(\S+)", "SNG Delepierre"),
        (r"SNG\s+Lewis[-\s]*(\S+)", "SNG Lewis"),
        (r"SNG\s+Switzerland[-\s]*(\S+)", "SNG Switzerland"),
        (r"SNG\s+France[-\s]*(\S+)", "SNG France"),
        (r"SNG\s+München[-\s]*(\S+)", "SNG München"),
        (r"SNG\s+Tübingen[-\s]*(\S+)", "SNG Tübingen"),
    ]

    for pattern, source in ref_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            refs.append(LotReference(
                source=source,
                number=match.group(1),
                full=match.group(0),
            ))

    return refs


def _parse_provenance_from_text(text: str) -> list[ProvenanceEntry]:
    """Extract provenance information from description text."""
    provenance: list[ProvenanceEntry] = []

    # Ex-collection patterns
    ex_patterns = [
        r"(?:Ex|From the)\s+(?:collection\s+of\s+)?([^.,;]+(?:Collection[^.,;]*))",
        r"Ex\s+(?:Auction\s+)?([^.,;]+(?:Auction[^.,;]*))",
        r"Ex\s+([^.,;]{10,60}?(?=,|\.|$))",
    ]

    for pattern in ex_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            prov_text = match.group(0).strip()
            if len(prov_text) > 10:  # filter noise
                ptype = "ex-auction" if "auction" in prov_text.lower() else "ex-collection"
                provenance.append(ProvenanceEntry(
                    text=prov_text,
                    type=ptype,
                ))

    return provenance


def parse_total_results(html: str) -> int:
    """Quick parse to extract just the total result count."""
    match = re.search(r"Results.*?of\s+([\d']+)\s+for", html)
    if match:
        return int(match.group(1).replace("'", ""))
    return 0
