"""Scrape Shelley Hardwoods products via WooCommerce Store API."""

import argparse
import csv
import html
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from datetime import date
from pathlib import Path
from typing import Dict, List, Tuple

USER_AGENT = "mentorr-scraper/1.0 (+https://github.com/mentorr)"

SPECIES_MAP = {
    "brazilian rosewood": "rosewood", "indian rosewood": "rosewood", "rosewood": "rosewood",
    "black walnut": "walnut", "walnut": "walnut", "big leaf maple": "maple", "maple": "maple",
    "rainbow alder": "alder", "spalted alder": "alder", "alder": "alder",
    "mahogany": "mahogany", "sapele": "sapele", "koa": "koa", "spruce": "spruce",
    "cedar": "cedar", "ebony": "ebony", "ash": "ash", "cherry": "cherry",
    "bubinga": "bubinga", "wenge": "wenge", "zebrawood": "zebrawood", "padauk": "padauk",
    "cocobolo": "cocobolo", "poplar": "poplar", "basswood": "basswood",
    "sycamore": "sycamore", "tamarind": "tamarind", "purpleheart": "purpleheart",
}

FIGURE_MAP = {
    "birdseye": "birdseye", "fiddleback": "fiddleback", "flame": "flame",
    "quilt": "quilt", "curly": "curly", "curl": "curly", "spalted": "spalted",
    "spalt": "spalted", "burled": "burl", "burl": "burl", "figured": "figured",
}

CATEGORY_FALLBACKS = [
    ("drop top", "Drop Top"), ("top set", "Top Set"), ("acoustic set", "Acoustic Set"),
    ("acoustic", "Acoustic Set"), ("neck blank", "Neck Blank"), ("neck", "Neck Blank"),
    ("body blank", "Body Blank"), ("body", "Body Blank"), ("fretboard", "Fretboard"),
    ("fingerboard", "Fretboard"), ("veneer", "Veneer"), ("binding", "Binding"),
]


def _fetch_json(url: str) -> Tuple[object, Dict[str, str]]:
    req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
    with urllib.request.urlopen(req, timeout=30) as resp:
        data = resp.read().decode("utf-8")
        headers = dict(resp.headers)
    return json.loads(data), headers


def _ascii_sanitize(text: str) -> str:
    if not text:
        return ""
    replacements = {
        "\u2013": "-", "\u2014": "-", "\u2018": "'", "\u2019": "'",
        "\u201c": '"', "\u201d": '"', "\u2032": "'", "\u2033": '"', "\u00d7": "x",
    }
    for old, new in replacements.items():
        text = text.replace(old, new)
    return text.encode("ascii", "ignore").decode("ascii")


def _strip_html(html_text: str) -> List[str]:
    if not html_text:
        return []
    cleaned = re.sub(r"<script.*?>.*?</script>", "", html_text, flags=re.S | re.I)
    cleaned = re.sub(r"<style.*?>.*?</style>", "", cleaned, flags=re.S | re.I)
    cleaned = re.sub(r"<[^>]+>", "\n", cleaned)
    cleaned = html.unescape(cleaned)
    cleaned = _ascii_sanitize(cleaned)
    return [line.strip() for line in cleaned.splitlines() if line.strip()]


def _detect_species(text: str) -> str:
    text = text.lower()
    keys = sorted(SPECIES_MAP.keys(), key=len, reverse=True)
    for key in keys:
        if key in text:
            return SPECIES_MAP[key]
    return ""


def _detect_figure(text: str) -> str:
    text = text.lower()
    found = []
    for key, value in FIGURE_MAP.items():
        if key in text and value not in found:
            found.append(value)
    return ", ".join(found)


def _pick_category(categories, name: str) -> str:
    if categories:
        def depth(cat):
            link = cat.get("link", "")
            if "/product-category/" in link:
                path = link.split("/product-category/")[1]
                return path.strip("/").count("/")
            return 999
        sorted_cats = sorted(categories, key=lambda cat: (depth(cat), len(cat.get("name", ""))))
        for cat in sorted_cats:
            name_value = _ascii_sanitize(cat.get("name", "")).strip()
            if name_value:
                return name_value
    lower = name.lower()
    for key, label in CATEGORY_FALLBACKS:
        if key in lower:
            return label
    return ""


def _format_price(prices) -> str:
    raw = str(prices.get("price", "")).strip()
    if not raw or raw == "0":
        return ""
    try:
        cents = int(raw)
    except ValueError:
        return ""
    if cents <= 0:
        return ""
    currency_symbol = str(prices.get("currency_symbol", "$") or "$")
    minor_unit = int(prices.get("currency_minor_unit", 2) or 2)
    value = cents / (10 ** minor_unit)
    return f"{currency_symbol}{value:.2f}"


def _product_to_row(product) -> Dict[str, str]:
    name = _ascii_sanitize(str(product.get("name", "")).strip())
    categories = product.get("categories") or []
    images = product.get("images") or []
    attributes = product.get("attributes") or []
    description_html = str(product.get("description", "") or "")
    short_description_html = str(product.get("short_description", "") or "")

    category = _pick_category(categories, name)
    text_blob = _ascii_sanitize(
        " ".join([name] + [cat.get("name", "") for cat in categories] + _strip_html(description_html))
    )

    species = _detect_species(text_blob)
    figure = _detect_figure(text_blob)
    price = _format_price(product.get("prices") or {})
    url = str(product.get("permalink", "")).strip()
    primary_image = ""
    if images:
        primary_image = str(images[0].get("src", "")).strip()

    return {
        "id": str(product.get("id", "")).strip(),
        "name": name,
        "seller": "Shelley Hardwoods",
        "species": species,
        "price": price,
        "listed_date": date.today().isoformat(),
        "status": "Active" if product.get("is_in_stock") else "Sold",
        "image_url": primary_image,
        "link": url,
        "dimensions": "",
        "figure": figure,
        "category": category,
        "raw_attributes": "",
    }


def _fetch_products(base_url: str, limit: int, per_page: int = 100, delay: float = 0.5) -> List[Dict[str, str]]:
    results = []
    page = 1
    total_pages = None
    while len(results) < limit:
        query = urllib.parse.urlencode({"per_page": per_page, "page": page})
        url = f"{base_url}/wp-json/wc/store/products?{query}"
        try:
            data, headers = _fetch_json(url)
        except Exception as exc:
            print(f"Failed to fetch page {page}: {exc}", file=sys.stderr)
            break
        if not data:
            break

        for product in data:
            if not isinstance(product, dict):
                continue
            if not product.get("is_in_stock", True):
                continue
            row = _product_to_row(product)
            if not row["name"] or not row["link"]:
                continue
            if not row["price"]:
                continue
            results.append(row)
            if len(results) >= limit:
                break

        if total_pages is None:
            total_pages = int(headers.get("X-WP-TotalPages", "0") or 0)
        if total_pages and page >= total_pages:
            break
        page += 1
        if delay > 0:
            time.sleep(delay)

    return results


def _write_csv(path: str, rows: List[Dict[str, str]]) -> None:
    fieldnames = [
        "id", "name", "seller", "species", "price", "listed_date",
        "status", "image_url", "link", "dimensions", "figure", "category", "raw_attributes",
    ]
    Path(path).parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


def run(data_dir: str, limit: int = 10000, base_url: str = "https://kimballhardwoods.com") -> str:
    """Run the Shelley Hardwoods scraper, writing output to data_dir/raw/shelley_latest.csv."""
    output_path = str(Path(data_dir) / "raw" / "shelley_latest.csv")
    rows = _fetch_products(base_url.rstrip("/"), limit)
    if not rows:
        return "No products returned from Shelley Hardwoods."
    _write_csv(output_path, rows)
    return f"Wrote {len(rows)} products to {output_path}"


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Scrape Shelley Hardwoods products.")
    parser.add_argument("--limit", type=int, default=100)
    parser.add_argument("--output", default="data/raw/shelley_latest.csv")
    parser.add_argument("--base-url", default="https://kimballhardwoods.com")
    args = parser.parse_args()
    rows = _fetch_products(args.base_url.rstrip("/"), args.limit)
    if rows:
        _write_csv(args.output, rows)
        print(f"Wrote {len(rows)} products to {args.output}")
    else:
        print("No products returned.", file=sys.stderr)
