"""
Phase 87: Global Bidding — Real-Time Auction for Tasks Across the Network.

Storage:
  state/broker/auctions/{auction_id}.json
  state/broker/auctions/history.jsonl
"""

import json
import logging
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional

from brynq.server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

logger = logging.getLogger(__name__)

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

AUCTIONS_DIR = BROKER_DIR / "auctions"
HISTORY_FILE = AUCTIONS_DIR / "history.jsonl"

AUCTIONS_DIR.mkdir(parents=True, exist_ok=True)

# ── Helpers ──────────────────────────────────────────────────────────────

def _auction_path(auction_id: str) -> Path:
    return AUCTIONS_DIR / f"{auction_id}.json"


def _load_auction(auction_id: str) -> Optional[dict]:
    p = _auction_path(auction_id)
    if not p.exists():
        return None
    try:
        return json.loads(p.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _save_auction(auction: dict) -> None:
    _write_json_sync(_auction_path(auction["auction_id"]), auction)


def _score_bid(bid: dict, required_skills: List[str]) -> float:
    """Score a bid: lower price is better, higher reputation is better,
    skill match ratio is a multiplier."""
    price = bid.get("bid_amount", 0)
    reputation = bid.get("reputation", 0)
    agent_skills = set(bid.get("skills", []))
    required = set(required_skills)
    skill_match = len(agent_skills & required) / max(len(required), 1)
    # Score: reputation weight + skill match bonus - price penalty
    return (reputation * 0.4) + (skill_match * 100 * 0.4) - (price * 0.2)


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


# ── Public API ───────────────────────────────────────────────────────────

def create_auction(
    task_id: str,
    title: str,
    required_skills: Optional[List[str]] = None,
    min_reputation: int = 0,
    deadline_minutes: int = 10,
) -> dict:
    """Open an auction for a task."""
    auction_id = uuid.uuid4().hex[:12]
    now = _now_iso()
    deadline_ts = time.time() + (deadline_minutes * 60)
    auction = {
        "auction_id": auction_id,
        "task_id": task_id,
        "title": title,
        "required_skills": required_skills or [],
        "min_reputation": min_reputation,
        "deadline_minutes": deadline_minutes,
        "deadline_ts": deadline_ts,
        "status": "open",
        "bids": [],
        "winner_id": None,
        "winning_bid": None,
        "created_at": now,
        "closed_at": None,
    }
    _save_auction(auction)
    return auction


def place_bid(
    auction_id: str,
    agent_id: str,
    bid_amount: float,
    pitch: str = "",
    estimated_minutes: int = 0,
    reputation: int = 0,
    skills: Optional[List[str]] = None,
) -> dict:
    """Agent places a bid on an open auction."""
    auction = _load_auction(auction_id)
    if auction is None:
        return {"error": f"Auction {auction_id} not found"}
    if auction["status"] != "open":
        return {"error": f"Auction is {auction['status']}, not open"}
    if reputation < auction.get("min_reputation", 0):
        return {"error": f"Reputation {reputation} below minimum {auction['min_reputation']}"}

    # Prevent duplicate bids from same agent
    for existing in auction["bids"]:
        if existing["agent_id"] == agent_id:
            return {"error": f"Agent {agent_id} already bid on this auction"}

    bid_id = uuid.uuid4().hex[:8]
    bid = {
        "bid_id": bid_id,
        "agent_id": agent_id,
        "bid_amount": bid_amount,
        "pitch": pitch,
        "estimated_minutes": estimated_minutes,
        "reputation": reputation,
        "skills": skills or [],
        "placed_at": _now_iso(),
    }
    auction["bids"].append(bid)
    _save_auction(auction)
    return bid


def get_bids(auction_id: str) -> List[dict]:
    """List all bids sorted by score (best first)."""
    auction = _load_auction(auction_id)
    if auction is None:
        return []
    required = auction.get("required_skills", [])
    bids = list(auction["bids"])
    for b in bids:
        b["score"] = _score_bid(b, required)
    bids.sort(key=lambda b: b["score"], reverse=True)
    return bids


def close_auction(auction_id: str, winner_id: Optional[str] = None) -> dict:
    """Close an auction and award to winner. Auto-select best bid if no winner."""
    auction = _load_auction(auction_id)
    if auction is None:
        return {"error": f"Auction {auction_id} not found"}
    if auction["status"] != "open":
        return {"error": f"Auction already {auction['status']}"}
    if not auction["bids"]:
        auction["status"] = "cancelled"
        auction["closed_at"] = _now_iso()
        _save_auction(auction)
        _append_jsonl_sync(HISTORY_FILE, {
            "auction_id": auction_id, "status": "cancelled",
            "task_id": auction["task_id"], "closed_at": auction["closed_at"],
            "bids_count": 0,
        })
        return {"auction_id": auction_id, "status": "cancelled", "reason": "no bids"}

    scored = get_bids(auction_id)
    if winner_id:
        winner_bid = next((b for b in scored if b["agent_id"] == winner_id), None)
        if winner_bid is None:
            return {"error": f"Agent {winner_id} did not bid on this auction"}
    else:
        winner_bid = scored[0]
        winner_id = winner_bid["agent_id"]

    auction["status"] = "closed"
    auction["winner_id"] = winner_id
    auction["winning_bid"] = winner_bid
    auction["closed_at"] = _now_iso()
    _save_auction(auction)
    _append_jsonl_sync(HISTORY_FILE, {
        "auction_id": auction_id, "task_id": auction["task_id"],
        "status": "closed", "winner_id": winner_id,
        "winning_amount": winner_bid["bid_amount"],
        "bids_count": len(auction["bids"]),
        "closed_at": auction["closed_at"],
    })
    return {
        "auction_id": auction_id, "status": "closed",
        "winner_id": winner_id, "winning_bid": winner_bid,
    }


def get_auction(auction_id: str) -> Optional[dict]:
    """Return full auction data."""
    return _load_auction(auction_id)


def list_auctions(status: Optional[str] = None) -> List[dict]:
    """List all auctions, optionally filtered by status."""
    results = []
    for p in AUCTIONS_DIR.glob("*.json"):
        if p.name == "history.jsonl":
            continue
        try:
            a = json.loads(p.read_text("utf-8"))
            if status is None or a.get("status") == status:
                results.append(a)
        except (json.JSONDecodeError, OSError):
            continue
    results.sort(key=lambda a: a.get("created_at", ""), reverse=True)
    return results


def get_agent_bid_history(agent_id: str) -> dict:
    """Past bids and win rate for an agent."""
    total_bids, wins, bid_records = 0, 0, []
    for a in list_auctions():
        for bid in a.get("bids", []):
            if bid["agent_id"] == agent_id:
                total_bids += 1
                is_winner = a.get("winner_id") == agent_id
                if is_winner:
                    wins += 1
                bid_records.append({
                    "auction_id": a["auction_id"], "task_id": a["task_id"],
                    "bid_amount": bid["bid_amount"], "status": a["status"],
                    "won": is_winner, "placed_at": bid["placed_at"],
                })
    win_rate = (wins / total_bids * 100) if total_bids > 0 else 0.0
    return {"agent_id": agent_id, "total_bids": total_bids, "wins": wins,
            "win_rate": round(win_rate, 1), "bids": bid_records}


def get_auction_stats() -> dict:
    """Aggregate stats: total auctions, avg bids, avg winning bid, fill rate."""
    all_auctions = list_auctions()
    total = len(all_auctions)
    if total == 0:
        return {"total_auctions": 0, "avg_bids_per_auction": 0,
                "avg_winning_bid": 0, "fill_rate": 0}

    total_bids = sum(len(a.get("bids", [])) for a in all_auctions)
    closed = [a for a in all_auctions if a.get("status") == "closed"]
    cancelled = [a for a in all_auctions if a.get("status") == "cancelled"]
    filled = len(closed)
    winning_amounts = [
        a["winning_bid"]["bid_amount"]
        for a in closed if a.get("winning_bid")
    ]
    avg_winning = sum(winning_amounts) / len(winning_amounts) if winning_amounts else 0
    fill_rate = (filled / (filled + len(cancelled)) * 100) if (filled + len(cancelled)) > 0 else 0

    return {"total_auctions": total, "open_auctions": total - filled - len(cancelled),
            "closed_auctions": filled, "cancelled_auctions": len(cancelled),
            "avg_bids_per_auction": round(total_bids / total, 1),
            "avg_winning_bid": round(avg_winning, 2), "fill_rate": round(fill_rate, 1)}
