"""
Tests for Phase 87: Global Bidding — Real-Time Auction System.

Covers: auction creation, bid placement, scoring, closing, history,
statistics, edge cases, and duplicate prevention.
"""

import json
import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all auction storage to tmp_path."""
    auctions_dir = tmp_path / "auctions"
    auctions_dir.mkdir(parents=True, exist_ok=True)

    import brynq.global_bidding as gb
    monkeypatch.setattr(gb, "AUCTIONS_DIR", auctions_dir)
    monkeypatch.setattr(gb, "HISTORY_FILE", auctions_dir / "history.jsonl")


# ── 1. Auction Creation ─────────────────────────────────────────────────


class TestCreateAuction:
    def test_create_basic(self):
        from brynq.global_bidding import create_auction
        a = create_auction("task-1", "Build widget")
        assert a["task_id"] == "task-1"
        assert a["title"] == "Build widget"
        assert a["status"] == "open"
        assert a["bids"] == []
        assert a["winner_id"] is None
        assert "auction_id" in a

    def test_create_with_skills(self):
        from brynq.global_bidding import create_auction
        a = create_auction("task-2", "Deploy", required_skills=["python", "docker"])
        assert a["required_skills"] == ["python", "docker"]

    def test_create_with_min_reputation(self):
        from brynq.global_bidding import create_auction
        a = create_auction("task-3", "Review", min_reputation=50)
        assert a["min_reputation"] == 50

    def test_create_with_custom_deadline(self):
        from brynq.global_bidding import create_auction
        a = create_auction("task-4", "Quick job", deadline_minutes=5)
        assert a["deadline_minutes"] == 5


# ── 2. Bid Placement ────────────────────────────────────────────────────


class TestPlaceBid:
    def test_place_valid_bid(self):
        from brynq.global_bidding import create_auction, place_bid
        a = create_auction("task-5", "Code review")
        bid = place_bid(a["auction_id"], "agent-a", 100, pitch="I'm fast")
        assert bid["agent_id"] == "agent-a"
        assert bid["bid_amount"] == 100
        assert bid["pitch"] == "I'm fast"
        assert "bid_id" in bid

    def test_place_bid_with_skills(self):
        from brynq.global_bidding import create_auction, place_bid
        a = create_auction("task-6", "ML task", required_skills=["ml", "python"])
        bid = place_bid(a["auction_id"], "agent-b", 50, skills=["ml", "python"],
                        reputation=80)
        assert bid["skills"] == ["ml", "python"]
        assert bid["reputation"] == 80

    def test_bid_on_nonexistent_auction(self):
        from brynq.global_bidding import place_bid
        result = place_bid("fake-id", "agent-c", 100)
        assert "error" in result

    def test_duplicate_bid_rejected(self):
        from brynq.global_bidding import create_auction, place_bid
        a = create_auction("task-7", "Dedupe test")
        place_bid(a["auction_id"], "agent-d", 100)
        result = place_bid(a["auction_id"], "agent-d", 200)
        assert "error" in result
        assert "already bid" in result["error"]

    def test_bid_below_min_reputation(self):
        from brynq.global_bidding import create_auction, place_bid
        a = create_auction("task-8", "Expert only", min_reputation=50)
        result = place_bid(a["auction_id"], "agent-e", 100, reputation=10)
        assert "error" in result
        assert "Reputation" in result["error"]

    def test_bid_on_closed_auction(self):
        from brynq.global_bidding import create_auction, place_bid, close_auction
        a = create_auction("task-9", "Closed test")
        place_bid(a["auction_id"], "agent-f", 100)
        close_auction(a["auction_id"])
        result = place_bid(a["auction_id"], "agent-g", 200)
        assert "error" in result

    def test_multiple_bids_different_agents(self):
        from brynq.global_bidding import create_auction, place_bid, get_auction
        a = create_auction("task-10", "Multi bid")
        place_bid(a["auction_id"], "agent-h", 100)
        place_bid(a["auction_id"], "agent-i", 200)
        place_bid(a["auction_id"], "agent-j", 150)
        auction = get_auction(a["auction_id"])
        assert len(auction["bids"]) == 3


# ── 3. Bid Scoring & Retrieval ──────────────────────────────────────────


class TestGetBids:
    def test_bids_sorted_by_score(self):
        from brynq.global_bidding import create_auction, place_bid, get_bids
        a = create_auction("task-11", "Score test", required_skills=["python"])
        place_bid(a["auction_id"], "low-rep", 100, reputation=10, skills=[])
        place_bid(a["auction_id"], "high-rep", 50, reputation=90,
                  skills=["python"])
        bids = get_bids(a["auction_id"])
        assert bids[0]["agent_id"] == "high-rep"
        assert all("score" in b for b in bids)

    def test_empty_bids(self):
        from brynq.global_bidding import create_auction, get_bids
        a = create_auction("task-12", "Empty")
        assert get_bids(a["auction_id"]) == []

    def test_bids_nonexistent_auction(self):
        from brynq.global_bidding import get_bids
        assert get_bids("fake") == []


# ── 4. Close Auction ────────────────────────────────────────────────────


class TestCloseAuction:
    def test_auto_select_winner(self):
        from brynq.global_bidding import create_auction, place_bid, close_auction
        a = create_auction("task-13", "Auto winner", required_skills=["go"])
        place_bid(a["auction_id"], "agent-k", 50, reputation=80, skills=["go"])
        place_bid(a["auction_id"], "agent-l", 100, reputation=20, skills=[])
        result = close_auction(a["auction_id"])
        assert result["status"] == "closed"
        assert result["winner_id"] == "agent-k"

    def test_manual_winner_selection(self):
        from brynq.global_bidding import create_auction, place_bid, close_auction
        a = create_auction("task-14", "Manual winner")
        place_bid(a["auction_id"], "agent-m", 50)
        place_bid(a["auction_id"], "agent-n", 100)
        result = close_auction(a["auction_id"], winner_id="agent-n")
        assert result["winner_id"] == "agent-n"

    def test_close_no_bids_cancels(self):
        from brynq.global_bidding import create_auction, close_auction
        a = create_auction("task-15", "No bids")
        result = close_auction(a["auction_id"])
        assert result["status"] == "cancelled"

    def test_close_nonexistent(self):
        from brynq.global_bidding import close_auction
        result = close_auction("fake")
        assert "error" in result

    def test_close_already_closed(self):
        from brynq.global_bidding import create_auction, place_bid, close_auction
        a = create_auction("task-16", "Double close")
        place_bid(a["auction_id"], "agent-o", 50)
        close_auction(a["auction_id"])
        result = close_auction(a["auction_id"])
        assert "error" in result

    def test_close_invalid_winner(self):
        from brynq.global_bidding import create_auction, place_bid, close_auction
        a = create_auction("task-17", "Bad winner")
        place_bid(a["auction_id"], "agent-p", 50)
        result = close_auction(a["auction_id"], winner_id="agent-nobody")
        assert "error" in result


# ── 5. Get / List Auctions ──────────────────────────────────────────────


class TestGetListAuctions:
    def test_get_auction(self):
        from brynq.global_bidding import create_auction, get_auction
        a = create_auction("task-18", "Get test")
        loaded = get_auction(a["auction_id"])
        assert loaded["task_id"] == "task-18"

    def test_get_nonexistent(self):
        from brynq.global_bidding import get_auction
        assert get_auction("nope") is None

    def test_list_all(self):
        from brynq.global_bidding import create_auction, list_auctions
        create_auction("task-19", "A")
        create_auction("task-20", "B")
        assert len(list_auctions()) == 2

    def test_list_by_status(self):
        from brynq.global_bidding import (create_auction, place_bid,
                                           close_auction, list_auctions)
        a1 = create_auction("task-21", "Open one")
        a2 = create_auction("task-22", "Closed one")
        place_bid(a2["auction_id"], "agent-q", 50)
        close_auction(a2["auction_id"])
        assert len(list_auctions(status="open")) == 1
        assert len(list_auctions(status="closed")) == 1


# ── 6. Agent Bid History ────────────────────────────────────────────────


class TestAgentBidHistory:
    def test_agent_with_bids(self):
        from brynq.global_bidding import (create_auction, place_bid,
                                           close_auction, get_agent_bid_history)
        a1 = create_auction("task-23", "Won")
        a2 = create_auction("task-24", "Lost")
        place_bid(a1["auction_id"], "agent-r", 50, reputation=80)
        place_bid(a2["auction_id"], "agent-r", 50)
        place_bid(a2["auction_id"], "agent-s", 30, reputation=90)
        close_auction(a1["auction_id"])
        close_auction(a2["auction_id"])
        hist = get_agent_bid_history("agent-r")
        assert hist["total_bids"] == 2
        assert hist["wins"] == 1
        assert hist["win_rate"] == 50.0

    def test_agent_no_bids(self):
        from brynq.global_bidding import get_agent_bid_history
        hist = get_agent_bid_history("agent-nobody")
        assert hist["total_bids"] == 0
        assert hist["win_rate"] == 0.0


# ── 7. Auction Stats ────────────────────────────────────────────────────


class TestAuctionStats:
    def test_empty_stats(self):
        from brynq.global_bidding import get_auction_stats
        stats = get_auction_stats()
        assert stats["total_auctions"] == 0

    def test_stats_with_data(self):
        from brynq.global_bidding import (create_auction, place_bid,
                                           close_auction, get_auction_stats)
        a1 = create_auction("task-25", "S1")
        place_bid(a1["auction_id"], "agent-t", 100)
        place_bid(a1["auction_id"], "agent-u", 200)
        close_auction(a1["auction_id"])

        a2 = create_auction("task-26", "S2")
        close_auction(a2["auction_id"])  # cancelled, no bids

        stats = get_auction_stats()
        assert stats["total_auctions"] == 2
        assert stats["closed_auctions"] == 1
        assert stats["cancelled_auctions"] == 1
        assert stats["fill_rate"] == 50.0
        assert stats["avg_bids_per_auction"] == 1.0  # 2 bids / 2 auctions

    def test_stats_open_auctions(self):
        from brynq.global_bidding import create_auction, get_auction_stats
        create_auction("task-27", "Open")
        stats = get_auction_stats()
        assert stats["open_auctions"] == 1
