"""
Brynq Agent Marketplace — Upwork/Fiverr for AI agents.

Agents publish service listings with pricing tiers and SLA guarantees.
Other agents browse, book, and review services. The broker tracks the full
lifecycle: listed -> booked -> in_progress -> delivered -> reviewed.

Storage:
  state/broker/marketplace/services/{service_id}.json — service listings
  state/broker/marketplace/bookings/{booking_id}.json — active bookings
  state/broker/marketplace/reviews/{service_id}.jsonl — reviews per service
"""

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

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

# ── Paths ──────────────────────────────────────────────────────────────────

MARKETPLACE_DIR = BROKER_DIR / "marketplace"
SERVICES_DIR = MARKETPLACE_DIR / "services"
BOOKINGS_DIR = MARKETPLACE_DIR / "bookings"
REVIEWS_DIR = MARKETPLACE_DIR / "reviews"

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

# ── Constants ──────────────────────────────────────────────────────────────

VALID_CATEGORIES = (
    "code_review",
    "testing",
    "docs",
    "refactoring",
    "debugging",
    "architecture",
    "security_audit",
    "performance",
    "data_analysis",
    "devops",
    "general",
    "raw_compute",
)

VALID_PRICING_TIERS = ("free", "basic", "standard", "premium")

VALID_BOOKING_STATUSES = (
    "pending",
    "accepted",
    "in_progress",
    "delivered",
    "completed",
    "cancelled",
)

VALID_QUALITY_LEVELS = ("best_effort", "standard", "guaranteed", "premium")


# ── Internal Helpers ───────────────────────────────────────────────────────


def _read_service(service_id: str) -> Optional[dict]:
    """Read a service listing by ID. Returns None if not found or corrupt."""
    path = SERVICES_DIR / f"{service_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_service(service: dict) -> None:
    """Write a service listing to disk."""
    _write_json_sync(SERVICES_DIR / f"{service['service_id']}.json", service)


def _read_booking(booking_id: str) -> Optional[dict]:
    """Read a booking by ID. Returns None if not found or corrupt."""
    path = BOOKINGS_DIR / f"{booking_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_booking(booking: dict) -> None:
    """Write a booking to disk."""
    _write_json_sync(BOOKINGS_DIR / f"{booking['booking_id']}.json", booking)


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


def _notify_channel(channel: str, message: str, msg_type: str = "marketplace") -> None:
    """Post a marketplace notification to the broker channel."""
    entry = {
        "id": uuid.uuid4().hex[:12],
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": SESSION_ID,
        "session_name": SESSION_NAME,
        "platform": PLATFORM,
        "channel": channel,
        "msg_type": msg_type,
        "message": message,
    }
    _append_jsonl_sync(CHANNELS_DIR / f"{channel}.jsonl", entry)


# ── Service Listings ───────────────────────────────────────────────────────


def list_service(
    title: str,
    description: str,
    category: str,
    pricing_tier: str = "basic",
    sla: Optional[dict] = None,
    tags: Optional[list[str]] = None,
    channel: str = "marketplace",
) -> dict:
    """Publish a service listing to the marketplace.

    Args:
        title: Short name for the service (e.g. "Python Code Review").
        description: Detailed description of what the service delivers.
        category: Service category. One of: code_review, testing, docs,
            refactoring, debugging, architecture, security_audit,
            performance, data_analysis, devops, general.
        pricing_tier: One of 'free', 'basic', 'standard', 'premium'.
        sla: Optional SLA guarantees dict with keys:
            - response_time_minutes: max minutes to acknowledge booking
            - completion_time_minutes: max minutes to deliver
            - quality_level: one of 'best_effort', 'standard', 'guaranteed', 'premium'
        tags: Optional list of keyword tags for discoverability.
        channel: Broker channel for marketplace notifications.

    Returns:
        The created service listing dict.

    Raises:
        ValueError: If category or pricing_tier is invalid, or SLA
            contains invalid quality_level.
    """
    if category not in VALID_CATEGORIES:
        raise ValueError(
            f"Invalid category '{category}'. Must be one of: {', '.join(VALID_CATEGORIES)}"
        )
    if pricing_tier not in VALID_PRICING_TIERS:
        raise ValueError(
            f"Invalid pricing_tier '{pricing_tier}'. Must be one of: {', '.join(VALID_PRICING_TIERS)}"
        )

    # Validate and normalize SLA
    sla_record = {
        "response_time_minutes": 30,
        "completion_time_minutes": 120,
        "quality_level": "standard",
    }
    if sla:
        if "response_time_minutes" in sla:
            sla_record["response_time_minutes"] = int(sla["response_time_minutes"])
        if "completion_time_minutes" in sla:
            sla_record["completion_time_minutes"] = int(sla["completion_time_minutes"])
        if "quality_level" in sla:
            ql = sla["quality_level"]
            if ql not in VALID_QUALITY_LEVELS:
                raise ValueError(
                    f"Invalid quality_level '{ql}'. Must be one of: {', '.join(VALID_QUALITY_LEVELS)}"
                )
            sla_record["quality_level"] = ql

    service_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    service = {
        "service_id": service_id,
        "title": title,
        "description": description,
        "category": category,
        "pricing_tier": pricing_tier,
        "sla": sla_record,
        "tags": tags or [],
        "provider_id": SESSION_ID,
        "provider_name": SESSION_NAME,
        "provider_platform": PLATFORM,
        "status": "active",
        "booking_count": 0,
        "avg_rating": 0.0,
        "review_count": 0,
        "created": now,
        "updated": now,
    }

    _write_service(service)

    # Announce on the marketplace channel
    _notify_channel(
        channel,
        f"[SERVICE LISTED] {title} (id: {service_id})\n"
        f"  Category: {category} | Tier: {pricing_tier} | "
        f"SLA: respond in {sla_record['response_time_minutes']}m, "
        f"deliver in {sla_record['completion_time_minutes']}m, "
        f"quality: {sla_record['quality_level']}\n"
        f"  {description[:200]}\n"
        f"  Book with: book_service(service_id='{service_id}', requirements='...')",
    )

    return service


# ── Browsing ───────────────────────────────────────────────────────────────


def browse_services(
    category: Optional[str] = None,
    min_rating: float = 0.0,
    pricing_tier: Optional[str] = None,
    status: str = "active",
) -> list[dict]:
    """Browse available services in the marketplace.

    Args:
        category: Filter by category. None returns all categories.
        min_rating: Minimum average rating (0.0-5.0). Defaults to 0.0.
        pricing_tier: Filter by pricing tier. None returns all tiers.
        status: Filter by service status. Defaults to 'active'.

    Returns:
        List of matching service dicts, sorted by rating (highest first),
        then by booking count (most popular first).
    """
    services = []
    for f in sorted(SERVICES_DIR.glob("*.json")):
        try:
            data = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue

        if status and data.get("status") != status:
            continue
        if category and data.get("category") != category:
            continue
        if data.get("avg_rating", 0.0) < min_rating:
            continue
        if pricing_tier and data.get("pricing_tier") != pricing_tier:
            continue

        services.append(data)

    # Sort by rating descending, then by booking count descending
    services.sort(
        key=lambda s: (s.get("avg_rating", 0.0), s.get("booking_count", 0)),
        reverse=True,
    )
    return services


# ── Booking ────────────────────────────────────────────────────────────────


def book_service(
    service_id: str,
    requirements: str,
    channel: str = "marketplace",
) -> dict:
    """Book a service from an agent.

    Creates a booking record linking the client to the service provider,
    with the agreed SLA terms copied from the service listing.

    Args:
        service_id: The service to book.
        requirements: Description of what the client needs done.
        channel: Broker channel for notifications.

    Returns:
        The created booking dict, or an error dict if the service
        is not found or not active.
    """
    service = _read_service(service_id)
    if not service:
        return {"error": f"Service '{service_id}' not found"}
    if service.get("status") != "active":
        return {"error": f"Service '{service_id}' is {service.get('status')}, not accepting bookings"}

    booking_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    booking = {
        "booking_id": booking_id,
        "service_id": service_id,
        "service_title": service["title"],
        "client_id": SESSION_ID,
        "client_name": SESSION_NAME,
        "client_platform": PLATFORM,
        "provider_id": service["provider_id"],
        "provider_name": service["provider_name"],
        "provider_platform": service["provider_platform"],
        "requirements": requirements,
        "pricing_tier": service["pricing_tier"],
        "sla": service["sla"],
        "status": "pending",
        "deliverable": None,
        "created": now,
        "accepted_at": None,
        "delivered_at": None,
        "completed_at": None,
        "cancelled_at": None,
    }

    _write_booking(booking)

    # Increment booking count on the service
    service["booking_count"] = service.get("booking_count", 0) + 1
    service["updated"] = now
    _write_service(service)

    # Notify
    _notify_channel(
        channel,
        f"[BOOKING CREATED] {SESSION_NAME}@{PLATFORM} booked "
        f"'{service['title']}' (booking: {booking_id})\n"
        f"  Provider: {service['provider_name']}@{service['provider_platform']}\n"
        f"  SLA: respond in {service['sla']['response_time_minutes']}m, "
        f"deliver in {service['sla']['completion_time_minutes']}m\n"
        f"  Requirements: {requirements[:200]}",
    )

    return booking


# ── Booking Lifecycle ──────────────────────────────────────────────────────


def accept_booking(
    booking_id: str,
    channel: str = "marketplace",
) -> dict:
    """Accept a pending booking (called by the service provider).

    Args:
        booking_id: The booking to accept.
        channel: Broker channel for notifications.

    Returns:
        The updated booking dict, or an error dict.
    """
    booking = _read_booking(booking_id)
    if not booking:
        return {"error": f"Booking '{booking_id}' not found"}
    if booking["status"] != "pending":
        return {"error": f"Booking '{booking_id}' is {booking['status']}, cannot accept"}

    now = datetime.now(timezone.utc).isoformat()
    booking["status"] = "in_progress"
    booking["accepted_at"] = now
    _write_booking(booking)

    _notify_channel(
        channel,
        f"[BOOKING ACCEPTED] {booking['provider_name']} accepted booking {booking_id} "
        f"for '{booking['service_title']}'",
    )

    return booking


def complete_booking(
    booking_id: str,
    deliverable: str,
    channel: str = "marketplace",
) -> dict:
    """Mark a booking as delivered with the deliverable output.

    The provider calls this when the work is done. The booking moves
    to 'delivered' status, awaiting client review.

    Args:
        booking_id: The booking to complete.
        deliverable: Description or content of what was delivered.
        channel: Broker channel for notifications.

    Returns:
        The updated booking dict, or an error dict.
    """
    booking = _read_booking(booking_id)
    if not booking:
        return {"error": f"Booking '{booking_id}' not found"}
    if booking["status"] not in ("pending", "accepted", "in_progress"):
        return {
            "error": f"Booking '{booking_id}' is {booking['status']}, cannot deliver"
        }

    now = datetime.now(timezone.utc).isoformat()
    booking["status"] = "delivered"
    booking["deliverable"] = deliverable
    booking["delivered_at"] = now
    _write_booking(booking)

    _notify_channel(
        channel,
        f"[BOOKING DELIVERED] {booking['provider_name']} delivered booking {booking_id} "
        f"for '{booking['service_title']}'\n"
        f"  Deliverable: {deliverable[:200]}",
    )

    return booking


def cancel_booking(
    booking_id: str,
    reason: str = "",
    channel: str = "marketplace",
) -> dict:
    """Cancel a booking.

    Args:
        booking_id: The booking to cancel.
        reason: Optional reason for cancellation.
        channel: Broker channel for notifications.

    Returns:
        The updated booking dict, or an error dict.
    """
    booking = _read_booking(booking_id)
    if not booking:
        return {"error": f"Booking '{booking_id}' not found"}
    if booking["status"] in ("completed", "cancelled"):
        return {"error": f"Booking '{booking_id}' is already {booking['status']}"}

    now = datetime.now(timezone.utc).isoformat()
    booking["status"] = "cancelled"
    booking["cancelled_at"] = now
    if reason:
        booking["cancel_reason"] = reason
    _write_booking(booking)

    _notify_channel(
        channel,
        f"[BOOKING CANCELLED] Booking {booking_id} for '{booking['service_title']}' cancelled"
        + (f": {reason[:200]}" if reason else ""),
    )

    return booking


# ── Reviews ────────────────────────────────────────────────────────────────


def review_service(
    booking_id: str,
    rating: float,
    review_text: str,
    channel: str = "marketplace",
) -> dict:
    """Leave a review after service delivery.

    The client calls this after a booking has been delivered. The booking
    is finalized to 'completed' status and a review record is appended
    to the service's review log.

    Args:
        booking_id: The delivered booking to review.
        rating: Rating from 1.0 to 5.0.
        review_text: Written review of the service.
        channel: Broker channel for notifications.

    Returns:
        The review dict, or an error dict.

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

    booking = _read_booking(booking_id)
    if not booking:
        return {"error": f"Booking '{booking_id}' not found"}
    if booking["status"] not in ("delivered", "in_progress", "accepted"):
        return {
            "error": f"Booking '{booking_id}' is {booking['status']}, "
            f"must be delivered before reviewing"
        }

    service_id = booking["service_id"]
    service = _read_service(service_id)
    if not service:
        return {"error": f"Service '{service_id}' not found (orphaned booking)"}

    now = datetime.now(timezone.utc).isoformat()

    # Create review record
    review = {
        "review_id": uuid.uuid4().hex[:12],
        "booking_id": booking_id,
        "service_id": service_id,
        "reviewer_id": booking["client_id"],
        "reviewer_name": booking["client_name"],
        "reviewer_platform": booking["client_platform"],
        "provider_id": booking["provider_id"],
        "provider_name": booking["provider_name"],
        "rating": round(rating, 1),
        "review_text": review_text,
        "pricing_tier": booking["pricing_tier"],
        "sla_met": _check_sla_compliance(booking),
        "created": now,
    }

    # Append to service review log
    reviews_path = REVIEWS_DIR / f"{service_id}.jsonl"
    _append_jsonl_sync(reviews_path, review)

    # Update service aggregate stats
    all_reviews = _read_reviews(service_id)
    total_ratings = sum(r.get("rating", 0.0) for r in all_reviews)
    review_count = len(all_reviews)
    service["avg_rating"] = round(total_ratings / max(review_count, 1), 2)
    service["review_count"] = review_count
    service["updated"] = now
    _write_service(service)

    # Finalize booking to completed
    booking["status"] = "completed"
    booking["completed_at"] = now
    _write_booking(booking)

    # Notify
    stars = "*" * int(round(rating))
    _notify_channel(
        channel,
        f"[REVIEW] {booking['client_name']} reviewed '{service['title']}' "
        f"({stars} {rating}/5.0)\n"
        f"  Provider: {service['provider_name']} | "
        f"SLA met: {'yes' if review['sla_met'] else 'no'}\n"
        f"  {review_text[:200]}",
    )

    return review


def _check_sla_compliance(booking: dict) -> bool:
    """Check if the booking met its SLA deadlines.

    Compares actual response and delivery times against the SLA
    guarantees. Returns True if all met or if timestamps are missing
    (benefit of the doubt).
    """
    sla = booking.get("sla", {})
    created = booking.get("created")
    accepted_at = booking.get("accepted_at")
    delivered_at = booking.get("delivered_at")

    if not created:
        return True

    try:
        created_dt = datetime.fromisoformat(created)
    except (ValueError, TypeError):
        return True

    # Check response time SLA
    max_response_min = sla.get("response_time_minutes", 0)
    if accepted_at and max_response_min > 0:
        try:
            accepted_dt = datetime.fromisoformat(accepted_at)
            response_min = (accepted_dt - created_dt).total_seconds() / 60
            if response_min > max_response_min:
                return False
        except (ValueError, TypeError):
            pass

    # Check completion time SLA
    max_completion_min = sla.get("completion_time_minutes", 0)
    if delivered_at and max_completion_min > 0:
        try:
            delivered_dt = datetime.fromisoformat(delivered_at)
            completion_min = (delivered_dt - created_dt).total_seconds() / 60
            if completion_min > max_completion_min:
                return False
        except (ValueError, TypeError):
            pass

    return True


# ── Service Stats ──────────────────────────────────────────────────────────


def get_service_stats(service_id: str) -> dict:
    """Get comprehensive statistics for a service listing.

    Args:
        service_id: The service to get stats for.

    Returns:
        A stats dict with booking count, average rating, completion rate,
        SLA compliance rate, rating distribution, and recent reviews.
        Returns an error dict if the service is not found.
    """
    service = _read_service(service_id)
    if not service:
        return {"error": f"Service '{service_id}' not found"}

    reviews = _read_reviews(service_id)

    # Gather all bookings for this service
    bookings = []
    for f in sorted(BOOKINGS_DIR.glob("*.json")):
        try:
            b = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue
        if b.get("service_id") == service_id:
            bookings.append(b)

    total_bookings = len(bookings)
    completed = [b for b in bookings if b.get("status") == "completed"]
    delivered = [b for b in bookings if b.get("status") == "delivered"]
    cancelled = [b for b in bookings if b.get("status") == "cancelled"]
    in_progress = [b for b in bookings if b.get("status") in ("pending", "accepted", "in_progress")]

    completed_count = len(completed) + len(delivered)
    completion_rate = round(completed_count / max(total_bookings, 1) * 100, 1)

    # SLA compliance from reviews
    sla_met_count = sum(1 for r in reviews if r.get("sla_met", False))
    sla_compliance_rate = round(sla_met_count / max(len(reviews), 1) * 100, 1)

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

    # Recent reviews (last 10)
    recent = sorted(reviews, key=lambda r: r.get("created", ""), reverse=True)[:10]

    return {
        "service_id": service_id,
        "title": service.get("title", ""),
        "provider_name": service.get("provider_name", ""),
        "category": service.get("category", ""),
        "pricing_tier": service.get("pricing_tier", ""),
        "status": service.get("status", ""),
        "sla": service.get("sla", {}),
        "total_bookings": total_bookings,
        "completed_bookings": completed_count,
        "cancelled_bookings": len(cancelled),
        "active_bookings": len(in_progress),
        "completion_rate": completion_rate,
        "avg_rating": service.get("avg_rating", 0.0),
        "review_count": len(reviews),
        "sla_compliance_rate": sla_compliance_rate,
        "rating_distribution": rating_dist,
        "recent_reviews": recent,
    }


# ── Service Management ─────────────────────────────────────────────────────


def pause_service(service_id: str) -> dict:
    """Pause a service listing (stop accepting new bookings).

    Args:
        service_id: The service to pause.

    Returns:
        The updated service dict, or an error dict.
    """
    service = _read_service(service_id)
    if not service:
        return {"error": f"Service '{service_id}' not found"}

    service["status"] = "paused"
    service["updated"] = datetime.now(timezone.utc).isoformat()
    _write_service(service)
    return service


def resume_service(service_id: str) -> dict:
    """Resume a paused service listing.

    Args:
        service_id: The service to resume.

    Returns:
        The updated service dict, or an error dict.
    """
    service = _read_service(service_id)
    if not service:
        return {"error": f"Service '{service_id}' not found"}

    service["status"] = "active"
    service["updated"] = datetime.now(timezone.utc).isoformat()
    _write_service(service)
    return service


def get_service_history(
    provider_id: Optional[str] = None,
    client_id: Optional[str] = None,
    status: Optional[str] = None,
) -> list[dict]:
    """Get booking history, optionally filtered by provider, client, or status.

    Args:
        provider_id: Filter bookings by provider session ID.
        client_id: Filter bookings by client session ID.
        status: Filter by booking status.

    Returns:
        List of matching booking dicts, sorted by creation time
        (newest first).
    """
    bookings = []
    for f in sorted(BOOKINGS_DIR.glob("*.json")):
        try:
            b = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue

        if provider_id and b.get("provider_id") != provider_id:
            continue
        if client_id and b.get("client_id") != client_id:
            continue
        if status and b.get("status") != status:
            continue

        bookings.append(b)

    bookings.sort(key=lambda b: b.get("created", ""), reverse=True)
    return bookings
