#!/usr/bin/env python3
"""Reproduce the five-shop Etsy competitor research aggregates."""

from __future__ import annotations

import argparse
import csv
import hashlib
import json
import re
import statistics
from collections import defaultdict
from datetime import datetime
from pathlib import Path
from typing import Any


SHOPS = [
    "GennybooFinds",
    "Comptonclothes",
    "Fangfangstory",
    "StarArtisans",
    "PurpleForestUS",
]
THEMES = {
    "shipping": re.compile(r"\b(ship|shipping|delivery|delivered|arriv|mail|postage|transit)\w*", re.I),
    "quality": re.compile(r"\b(quality|crafted|craftsmanship|made|sturdy|beautifully made|well made)\b", re.I),
    "packaging": re.compile(r"\b(packag|packed|wrapp|box)\w*", re.I),
    "communication": re.compile(r"\b(communicat|message|seller|customer service|responsive|response)\w*", re.I),
    "appearance": re.compile(r"\b(beautiful|gorgeous|cute|pretty|stunning|color|colour|look|design)\w*", re.I),
    "gift": re.compile(r"\b(gift|present|birthday|christmas|anniversary)\w*", re.I),
    "damage_or_defect": re.compile(r"\b(broken|damage|defect|crack|chip|not work|stopped working|missing)\w*", re.I),
    "size": re.compile(r"\b(size|small|smaller|large|larger|tiny|dimensions?)\b", re.I),
}
MUSHROOM = re.compile(r"\b(mushroom|fungi|toadstool)\b", re.I)
LIGHTING = re.compile(r"\b(lamp|light|lighting|nightlight|night light)\b", re.I)


def args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("search_csv", type=Path)
    parser.add_argument("catalog_json", type=Path)
    parser.add_argument("products_json", type=Path)
    parser.add_argument("reviews_json", type=Path)
    return parser.parse_args()


def read_json(path: Path) -> list[dict[str, Any]]:
    value = json.loads(path.read_text(encoding="utf-8"))
    if isinstance(value, dict) and "items" in value:
        value = value["items"]
    if not isinstance(value, list):
        raise SystemExit(f"Expected a JSON array or items object: {path}")
    return value


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def number(value: Any) -> float | None:
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def truth(value: Any) -> bool:
    return value is True or str(value).strip().lower() == "true"


def median(values: list[float], digits: int = 2) -> float | None:
    return round(statistics.median(values), digits) if values else None


def percentage(count: int, total: int) -> float:
    return round(100 * count / total, 1) if total else 0.0


def date_key(value: str) -> datetime:
    for pattern in ("%b %d, %Y", "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%dT%H:%M:%S%z"):
        try:
            return datetime.strptime(value.replace("Z", "+00:00"), pattern)
        except ValueError:
            pass
    raise ValueError(f"Unsupported date: {value}")


def main() -> None:
    options = args()
    with options.search_csv.open(encoding="utf-8-sig", newline="") as handle:
        search = list(csv.DictReader(handle))
    catalog = read_json(options.catalog_json)
    products = read_json(options.products_json)
    reviews = read_json(options.reviews_json)

    output: dict[str, Any] = {
        "study": "etsy-competitor-analysis-five-shops",
        "source_sha256": {
            "search": sha256(options.search_csv),
            "catalog": sha256(options.catalog_json),
            "products": sha256(options.products_json),
            "reviews": sha256(options.reviews_json),
        },
        "counts": {
            "search_rows": len(search),
            "catalog_rows": len(catalog),
            "product_rows": len(products),
            "review_rows": len(reviews),
            "total_rows": len(search) + len(catalog) + len(products) + len(reviews),
        },
        "shops": {},
    }

    for shop in SHOPS:
        search_rows = [row for row in search if row.get("shop_name") == shop]
        catalog_rows = [row for row in catalog if row.get("shop") == shop]
        product_rows = [row for row in products if row.get("shop_name") == shop]
        review_rows = [row for row in reviews if row.get("shop") == shop]

        by_listing: dict[str, list[dict[str, Any]]] = defaultdict(list)
        for row in search_rows:
            by_listing[row["listing_id"]].append(row)
        first_seen = {
            listing_id: min(rows, key=lambda row: int(float(row["rank"])))
            for listing_id, rows in by_listing.items()
        }
        placements = {"sponsored_only": 0, "organic_only": 0, "both": 0}
        for rows in by_listing.values():
            sponsored = any(truth(row.get("is_ad")) for row in rows)
            organic = any(not truth(row.get("is_ad")) for row in rows)
            placements["both" if sponsored and organic else "sponsored_only" if sponsored else "organic_only"] += 1

        catalog_prices = [price for row in catalog_rows if (price := number(row.get("price"))) is not None]
        mushroom_lighting = sum(
            bool(MUSHROOM.search(row.get("title", "")) and LIGHTING.search(row.get("title", "")))
            for row in catalog_rows
        )
        product_prices = [price for row in product_rows if (price := number(row.get("price"))) is not None]
        discounts = [discount for row in product_rows if (discount := number(row.get("discount_pct"))) is not None and discount > 0]
        ratings = [rating for row in review_rows if (rating := number(row.get("rating"))) is not None]
        review_dates = sorted(date_key(row["review_date"]) for row in review_rows)

        output["shops"][shop] = {
            "search": {
                "captured_rows": len(search_rows),
                "unique_listings": len(first_seen),
                "best_captured_rank": min(int(float(row["rank"])) for row in first_seen.values()),
                "median_first_seen_rank": median([float(row["rank"]) for row in first_seen.values()], 1),
                "placements": placements,
            },
            "catalog": {
                "listings": len(catalog_rows),
                "price_min": min(catalog_prices),
                "price_median": median(catalog_prices),
                "price_max": max(catalog_prices),
                "mushroom_lighting_titles": mushroom_lighting,
                "mushroom_lighting_pct": percentage(mushroom_lighting, len(catalog_rows)),
            },
            "products": {
                "sample_size": len(product_rows),
                "price_median": median(product_prices),
                "median_discount_pct": median(discounts, 1),
                "on_sale_pct": percentage(sum(truth(row.get("is_on_sale")) for row in product_rows), len(product_rows)),
                "free_shipping_pct": percentage(sum(truth(row.get("is_free_shipping")) for row in product_rows), len(product_rows)),
                "video_pct": percentage(sum(bool((row.get("video") or {}).get("url")) for row in product_rows), len(product_rows)),
            },
            "reviews": {
                "sample_size": len(review_rows),
                "date_min": review_dates[0].date().isoformat(),
                "date_max": review_dates[-1].date().isoformat(),
                "average_rating": round(statistics.mean(ratings), 2),
                "five_star_pct": percentage(sum(rating == 5 for rating in ratings), len(ratings)),
                "seller_response_pct": percentage(sum(bool(row.get("seller_response")) for row in review_rows), len(review_rows)),
                "unique_reviewed_listings": len({row.get("listing_id") for row in review_rows if row.get("listing_id")}),
                "theme_mentions": {
                    name: sum(bool(pattern.search(row.get("text") or "")) for row in review_rows)
                    for name, pattern in THEMES.items()
                },
            },
        }

    print(json.dumps(output, indent=2))


if __name__ == "__main__":
    main()
