"""
Phase 42: Template Marketplace — Workflow Templates & Swarm Blueprints.

A marketplace where agents publish, discover, and purchase reusable templates
including workflow definitions, swarm blueprints, agent configurations,
integration recipes, and automation scripts.

Revenue model: 15% platform fee on every paid sale (Google-model monetization).
Free templates are supported with zero-cost distribution.

Features:
  - PUBLISH: List templates with pricing, tags, and categories
  - BROWSE: Search/filter/sort the template catalog
  - PURCHASE: Credit-based transactions with 15% platform fee
  - REVIEWS: 1-5 star ratings with comments
  - STATS: Per-template and per-seller analytics
  - TRENDING: Time-windowed popularity tracking
  - VERSIONS: Publish updates with changelogs; buyers get latest

Storage:
  state/broker/template_market/
    templates/{id}.json        -- template listings
    reviews/{id}.jsonl         -- review log per template
    transactions.jsonl         -- append-only purchase log
"""

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from brynq.server import (
    BROKER_DIR,
    SESSION_ID,
    SESSION_NAME,
    PLATFORM,
    _write_json_sync,
    _read_jsonl_sync,
    _append_jsonl_sync,
)

# -- Paths -----------------------------------------------------------------

TEMPLATE_MARKET_DIR = BROKER_DIR / "template_market"
TEMPLATES_DIR = TEMPLATE_MARKET_DIR / "templates"
REVIEWS_DIR = TEMPLATE_MARKET_DIR / "reviews"
TRANSACTIONS_FILE = TEMPLATE_MARKET_DIR / "transactions.jsonl"

for _d in (TEMPLATES_DIR, REVIEWS_DIR):
    _d.mkdir(parents=True, exist_ok=True)

# -- Constants --------------------------------------------------------------

VALID_CATEGORIES = (
    "workflow",
    "swarm_blueprint",
    "agent_config",
    "integration",
    "automation",
)

VALID_SORT_OPTIONS = (
    "popular",
    "newest",
    "highest_rated",
    "price_low",
    "price_high",
)

PLATFORM_FEE_RATE = 0.15  # 15% platform fee


# -- Internal Helpers -------------------------------------------------------


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _read_template(template_id: str) -> Optional[dict]:
    """Read a template listing by ID. Returns None if missing/corrupt."""
    path = TEMPLATES_DIR / f"{template_id}.json"
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _write_template(template: dict) -> None:
    """Persist a template listing to disk."""
    _write_json_sync(TEMPLATES_DIR / f"{template['template_id']}.json", template)


def _read_reviews(template_id: str) -> list[dict]:
    """Read all reviews for a template."""
    path = REVIEWS_DIR / f"{template_id}.jsonl"
    return _read_jsonl_sync(path)


def _all_templates() -> list[dict]:
    """Load every template listing from disk."""
    templates: list[dict] = []
    for f in sorted(TEMPLATES_DIR.glob("*.json")):
        try:
            data = json.loads(f.read_text("utf-8"))
            templates.append(data)
        except (json.JSONDecodeError, OSError):
            continue
    return templates


def _all_transactions() -> list[dict]:
    """Load the full transaction log."""
    return _read_jsonl_sync(TRANSACTIONS_FILE)


# -- 1. Publish Template ----------------------------------------------------


def publish_template(
    title: str,
    description: str,
    category: str,
    template_data: dict,
    price_credits: float = 0,
    tags: Optional[list[str]] = None,
) -> dict:
    """Publish a template to the marketplace.

    Args:
        title: Human-readable template name.
        description: What this template does and how to use it.
        category: One of workflow, swarm_blueprint, agent_config,
            integration, automation.
        template_data: The actual template payload (workflow JSON,
            blueprint dict, config dict, etc.).
        price_credits: Price in credits. 0 = free.
        tags: Optional keyword tags for discoverability.

    Returns:
        The created template listing dict.

    Raises:
        ValueError: If category is invalid or price is negative.
    """
    if category not in VALID_CATEGORIES:
        raise ValueError(
            f"Invalid category '{category}'. "
            f"Must be one of: {', '.join(VALID_CATEGORIES)}"
        )
    if price_credits < 0:
        raise ValueError("price_credits cannot be negative")

    template_id = uuid.uuid4().hex[:12]
    now = _now_iso()

    template = {
        "template_id": template_id,
        "title": title,
        "description": description,
        "category": category,
        "template_data": template_data,
        "price_credits": round(float(price_credits), 2),
        "tags": tags or [],
        "seller_id": SESSION_ID,
        "seller_name": SESSION_NAME,
        "seller_platform": PLATFORM,
        "status": "active",
        "downloads": 0,
        "revenue": 0.0,
        "avg_rating": 0.0,
        "review_count": 0,
        "version": 1,
        "versions": [
            {
                "version": 1,
                "template_data": template_data,
                "changelog": "Initial release",
                "published": now,
            }
        ],
        "featured": False,
        "created": now,
        "updated": now,
    }

    _write_template(template)
    return template


# -- 2. Browse Templates ----------------------------------------------------


def browse_templates(
    category: Optional[str] = None,
    min_rating: float = 0,
    price: str = "all",
    query: Optional[str] = None,
    sort: str = "popular",
    limit: int = 20,
) -> list[dict]:
    """Browse and search the template catalog.

    Args:
        category: Filter by category. None returns all.
        min_rating: Minimum average rating (0-5).
        price: "all", "free", or "paid".
        query: Free-text search across title, description, tags.
        sort: One of popular, newest, highest_rated, price_low, price_high.
        limit: Maximum results to return.

    Returns:
        Filtered and sorted list of template dicts.
    """
    templates = _all_templates()

    # -- Filters --
    if category:
        templates = [t for t in templates if t.get("category") == category]

    templates = [t for t in templates if t.get("status") == "active"]

    if min_rating > 0:
        templates = [t for t in templates if t.get("avg_rating", 0) >= min_rating]

    if price == "free":
        templates = [t for t in templates if t.get("price_credits", 0) == 0]
    elif price == "paid":
        templates = [t for t in templates if t.get("price_credits", 0) > 0]

    if query:
        q = query.lower()
        filtered = []
        for t in templates:
            searchable = (
                t.get("title", "").lower()
                + " "
                + t.get("description", "").lower()
                + " "
                + " ".join(tag.lower() for tag in t.get("tags", []))
            )
            if q in searchable:
                filtered.append(t)
        templates = filtered

    # -- Sort --
    if sort == "popular":
        templates.sort(key=lambda t: t.get("downloads", 0), reverse=True)
    elif sort == "newest":
        templates.sort(key=lambda t: t.get("created", ""), reverse=True)
    elif sort == "highest_rated":
        templates.sort(key=lambda t: t.get("avg_rating", 0), reverse=True)
    elif sort == "price_low":
        templates.sort(key=lambda t: t.get("price_credits", 0))
    elif sort == "price_high":
        templates.sort(key=lambda t: t.get("price_credits", 0), reverse=True)

    return templates[:limit]


# -- 3. Purchase Template ---------------------------------------------------


def purchase_template(template_id: str, buyer_id: str) -> dict:
    """Purchase (or download for free) a template.

    For paid templates: deducts credits from buyer, credits seller minus
    15% platform fee, records the transaction.
    For free templates: increments download count.

    Args:
        template_id: The template to acquire.
        buyer_id: The agent purchasing the template.

    Returns:
        A purchase receipt dict, or an error dict.
    """
    template = _read_template(template_id)
    if not template:
        return {"error": f"Template '{template_id}' not found"}
    if template.get("status") != "active":
        return {"error": f"Template '{template_id}' is not active"}

    price = template.get("price_credits", 0)
    now = _now_iso()
    tx_id = uuid.uuid4().hex[:12]

    if price > 0:
        # Credit transaction: buyer pays, seller receives minus platform fee
        platform_fee = round(price * PLATFORM_FEE_RATE, 2)
        seller_payout = round(price - platform_fee, 2)

        tx = {
            "tx_id": tx_id,
            "template_id": template_id,
            "buyer_id": buyer_id,
            "seller_id": template["seller_id"],
            "price": price,
            "platform_fee": platform_fee,
            "seller_payout": seller_payout,
            "timestamp": now,
        }
        _append_jsonl_sync(TRANSACTIONS_FILE, tx)

        # Update template revenue
        template["revenue"] = round(template.get("revenue", 0) + seller_payout, 2)
    else:
        tx = {
            "tx_id": tx_id,
            "template_id": template_id,
            "buyer_id": buyer_id,
            "seller_id": template["seller_id"],
            "price": 0,
            "platform_fee": 0,
            "seller_payout": 0,
            "timestamp": now,
        }
        _append_jsonl_sync(TRANSACTIONS_FILE, tx)
        platform_fee = 0
        seller_payout = 0

    # Increment downloads
    template["downloads"] = template.get("downloads", 0) + 1
    template["updated"] = now
    _write_template(template)

    return {
        "tx_id": tx_id,
        "template_id": template_id,
        "title": template["title"],
        "buyer_id": buyer_id,
        "price": price,
        "platform_fee": platform_fee,
        "seller_payout": seller_payout,
        "template_data": template["template_data"],
    }


# -- 4. Reviews -------------------------------------------------------------


def review_template(
    template_id: str,
    rating: int,
    comment: str,
    reviewer_id: str,
) -> dict:
    """Leave a review for a template.

    Args:
        template_id: The template being reviewed.
        rating: Integer rating from 1 to 5.
        comment: Review text.
        reviewer_id: The reviewing agent's ID.

    Returns:
        The review record dict, or an error dict.

    Raises:
        ValueError: If rating is outside 1-5 range.
    """
    if not (1 <= rating <= 5):
        raise ValueError(f"Rating must be between 1 and 5, got {rating}")

    template = _read_template(template_id)
    if not template:
        return {"error": f"Template '{template_id}' not found"}

    now = _now_iso()
    review = {
        "review_id": uuid.uuid4().hex[:12],
        "template_id": template_id,
        "reviewer_id": reviewer_id,
        "rating": rating,
        "comment": comment,
        "created": now,
    }

    reviews_path = REVIEWS_DIR / f"{template_id}.jsonl"
    _append_jsonl_sync(reviews_path, review)

    # Recompute average rating
    all_reviews = _read_reviews(template_id)
    total = sum(r.get("rating", 0) for r in all_reviews)
    count = len(all_reviews)
    template["avg_rating"] = round(total / max(count, 1), 2)
    template["review_count"] = count
    template["updated"] = now
    _write_template(template)

    return review


def get_reviews(template_id: str) -> list[dict]:
    """Get all reviews for a template.

    Args:
        template_id: The template to get reviews for.

    Returns:
        List of review dicts sorted newest-first.
    """
    reviews = _read_reviews(template_id)
    reviews.sort(key=lambda r: r.get("created", ""), reverse=True)
    return reviews


# -- 5. Template Stats / Seller Stats --------------------------------------


def get_template_stats(template_id: str) -> dict:
    """Get analytics for a single template.

    Args:
        template_id: The template to get stats for.

    Returns:
        Stats dict with downloads, revenue, avg rating, review count.
        Returns an error dict if not found.
    """
    template = _read_template(template_id)
    if not template:
        return {"error": f"Template '{template_id}' not found"}

    reviews = _read_reviews(template_id)
    rating_dist = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
    for r in reviews:
        bucket = max(1, min(5, int(r.get("rating", 0))))
        rating_dist[bucket] += 1

    return {
        "template_id": template_id,
        "title": template.get("title", ""),
        "downloads": template.get("downloads", 0),
        "revenue": template.get("revenue", 0.0),
        "avg_rating": template.get("avg_rating", 0.0),
        "review_count": template.get("review_count", 0),
        "rating_distribution": rating_dist,
        "version": template.get("version", 1),
        "category": template.get("category", ""),
        "price_credits": template.get("price_credits", 0),
        "created": template.get("created", ""),
    }


def get_seller_stats(seller_id: str) -> dict:
    """Get aggregate stats across all templates published by a seller.

    Args:
        seller_id: The seller's session/agent ID.

    Returns:
        Aggregate stats: total templates, total revenue, avg rating, etc.
    """
    templates = [t for t in _all_templates() if t.get("seller_id") == seller_id]

    total_templates = len(templates)
    total_downloads = sum(t.get("downloads", 0) for t in templates)
    total_revenue = round(sum(t.get("revenue", 0) for t in templates), 2)

    ratings = [t.get("avg_rating", 0) for t in templates if t.get("review_count", 0) > 0]
    avg_rating = round(sum(ratings) / max(len(ratings), 1), 2) if ratings else 0.0

    total_reviews = sum(t.get("review_count", 0) for t in templates)

    return {
        "seller_id": seller_id,
        "total_templates": total_templates,
        "total_downloads": total_downloads,
        "total_revenue": total_revenue,
        "avg_rating": avg_rating,
        "total_reviews": total_reviews,
    }


# -- 6. Featured / Trending ------------------------------------------------


def get_featured(limit: int = 5) -> list[dict]:
    """Get featured templates (hand-picked or highest-rated).

    Templates explicitly marked as featured are returned first,
    then the highest-rated active templates fill the remaining slots.

    Args:
        limit: Max number of templates to return.

    Returns:
        List of featured template dicts.
    """
    templates = [t for t in _all_templates() if t.get("status") == "active"]

    # Featured templates first
    featured = [t for t in templates if t.get("featured")]
    non_featured = [t for t in templates if not t.get("featured")]

    # Sort non-featured by rating descending, then downloads descending
    non_featured.sort(
        key=lambda t: (t.get("avg_rating", 0), t.get("downloads", 0)),
        reverse=True,
    )

    result = featured + non_featured
    return result[:limit]


def get_trending(hours: int = 24, limit: int = 10) -> list[dict]:
    """Get trending templates based on recent downloads.

    Counts purchases/downloads within the given time window and returns
    the most popular ones.

    Args:
        hours: Look-back window in hours. Defaults to 24.
        limit: Max number of results.

    Returns:
        List of template dicts sorted by recent download count.
    """
    now = datetime.now(timezone.utc)
    cutoff_seconds = hours * 3600
    txs = _all_transactions()

    # Count recent downloads per template
    counts: dict[str, int] = {}
    for tx in txs:
        ts_str = tx.get("timestamp", "")
        try:
            ts = datetime.fromisoformat(ts_str)
            delta = (now - ts).total_seconds()
            if delta <= cutoff_seconds:
                tid = tx.get("template_id", "")
                counts[tid] = counts.get(tid, 0) + 1
        except (ValueError, TypeError):
            continue

    # Sort by count descending
    ranked = sorted(counts.items(), key=lambda kv: kv[1], reverse=True)

    results: list[dict] = []
    for tid, count in ranked[:limit]:
        tmpl = _read_template(tid)
        if tmpl and tmpl.get("status") == "active":
            tmpl["recent_downloads"] = count
            results.append(tmpl)

    return results


# -- 7. Version Management --------------------------------------------------


def update_template(
    template_id: str,
    new_data: dict,
    changelog: str,
) -> dict:
    """Publish a new version of a template.

    Args:
        template_id: The template to update.
        new_data: The updated template payload.
        changelog: Description of what changed in this version.

    Returns:
        The updated template dict, or an error dict.
    """
    template = _read_template(template_id)
    if not template:
        return {"error": f"Template '{template_id}' not found"}

    now = _now_iso()
    new_version = template.get("version", 1) + 1

    template["template_data"] = new_data
    template["version"] = new_version
    template["updated"] = now

    versions = template.get("versions", [])
    versions.append({
        "version": new_version,
        "template_data": new_data,
        "changelog": changelog,
        "published": now,
    })
    template["versions"] = versions

    _write_template(template)
    return template


def get_versions(template_id: str) -> list[dict]:
    """Get version history for a template.

    Args:
        template_id: The template to get versions for.

    Returns:
        List of version dicts (newest first), or an error dict.
    """
    template = _read_template(template_id)
    if not template:
        return {"error": f"Template '{template_id}' not found"}

    versions = template.get("versions", [])
    # Return newest first
    return sorted(versions, key=lambda v: v.get("version", 0), reverse=True)
