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

import json
import time

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all template marketplace storage to tmp_path."""
    market_dir = tmp_path / "template_market"
    templates_dir = market_dir / "templates"
    reviews_dir = market_dir / "reviews"
    for d in (market_dir, templates_dir, reviews_dir):
        d.mkdir(parents=True, exist_ok=True)

    # Patch the channels dir so _notify helpers don't fail
    channels_dir = tmp_path / "channels"
    channels_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(srv, "CHANNELS_DIR", channels_dir)

    import brynq.template_marketplace as tm
    monkeypatch.setattr(tm, "TEMPLATE_MARKET_DIR", market_dir)
    monkeypatch.setattr(tm, "TEMPLATES_DIR", templates_dir)
    monkeypatch.setattr(tm, "REVIEWS_DIR", reviews_dir)
    monkeypatch.setattr(tm, "TRANSACTIONS_FILE", market_dir / "transactions.jsonl")


def _make_template(**overrides):
    """Helper to publish a template with sensible defaults."""
    from brynq.template_marketplace import publish_template

    defaults = {
        "title": "Test Workflow",
        "description": "A test workflow template",
        "category": "workflow",
        "template_data": {"steps": [{"id": "s1", "action": "run_tests"}]},
        "price_credits": 0,
        "tags": ["test"],
    }
    defaults.update(overrides)
    return publish_template(**defaults)


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


class TestPublishTemplate:
    def test_publish_free_template(self):
        t = _make_template()
        assert t["template_id"]
        assert t["title"] == "Test Workflow"
        assert t["category"] == "workflow"
        assert t["price_credits"] == 0
        assert t["downloads"] == 0
        assert t["version"] == 1
        assert t["status"] == "active"
        assert len(t["versions"]) == 1

    def test_publish_paid_template(self):
        t = _make_template(title="Premium Blueprint", price_credits=50)
        assert t["price_credits"] == 50.0

    def test_publish_with_tags(self):
        t = _make_template(tags=["ci", "testing", "automation"])
        assert t["tags"] == ["ci", "testing", "automation"]

    def test_publish_invalid_category(self):
        with pytest.raises(ValueError, match="Invalid category"):
            _make_template(category="invalid_cat")

    def test_publish_negative_price(self):
        with pytest.raises(ValueError, match="negative"):
            _make_template(price_credits=-10)

    def test_publish_all_categories(self):
        from brynq.template_marketplace import VALID_CATEGORIES

        for cat in VALID_CATEGORIES:
            t = _make_template(title=f"Template {cat}", category=cat)
            assert t["category"] == cat


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


class TestBrowseTemplates:
    def test_browse_all(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="T1")
        _make_template(title="T2")
        results = browse_templates()
        assert len(results) == 2

    def test_browse_by_category(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="WF", category="workflow")
        _make_template(title="BP", category="swarm_blueprint")
        results = browse_templates(category="workflow")
        assert len(results) == 1
        assert results[0]["title"] == "WF"

    def test_browse_free_only(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="Free", price_credits=0)
        _make_template(title="Paid", price_credits=100)
        results = browse_templates(price="free")
        assert len(results) == 1
        assert results[0]["title"] == "Free"

    def test_browse_paid_only(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="Free", price_credits=0)
        _make_template(title="Paid", price_credits=100)
        results = browse_templates(price="paid")
        assert len(results) == 1
        assert results[0]["title"] == "Paid"

    def test_browse_with_query(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="CI Pipeline", description="Continuous integration")
        _make_template(title="Deploy Script", description="Kubernetes deployment")
        results = browse_templates(query="pipeline")
        assert len(results) == 1
        assert results[0]["title"] == "CI Pipeline"

    def test_browse_query_searches_tags(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="Something", tags=["kubernetes", "deploy"])
        _make_template(title="Other", tags=["python"])
        results = browse_templates(query="kubernetes")
        assert len(results) == 1

    def test_browse_min_rating(self):
        from brynq.template_marketplace import browse_templates, review_template

        t1 = _make_template(title="Good")
        t2 = _make_template(title="Bad")
        review_template(t1["template_id"], 5, "Great!", "r1")
        review_template(t2["template_id"], 2, "Meh", "r2")
        results = browse_templates(min_rating=4)
        assert len(results) == 1
        assert results[0]["title"] == "Good"

    def test_browse_sort_newest(self):
        from brynq.template_marketplace import browse_templates

        t1 = _make_template(title="First")
        t2 = _make_template(title="Second")
        results = browse_templates(sort="newest")
        # Second was created after First
        assert results[0]["title"] == "Second"

    def test_browse_sort_price_low(self):
        from brynq.template_marketplace import browse_templates

        _make_template(title="Expensive", price_credits=200)
        _make_template(title="Cheap", price_credits=10)
        results = browse_templates(sort="price_low")
        assert results[0]["title"] == "Cheap"

    def test_browse_limit(self):
        from brynq.template_marketplace import browse_templates

        for i in range(5):
            _make_template(title=f"T{i}")
        results = browse_templates(limit=3)
        assert len(results) == 3


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


class TestPurchaseTemplate:
    def test_purchase_free_template(self):
        from brynq.template_marketplace import purchase_template

        t = _make_template(title="Free Thing", price_credits=0)
        result = purchase_template(t["template_id"], "buyer-1")
        assert result["price"] == 0
        assert result["platform_fee"] == 0
        assert "template_data" in result
        assert result["tx_id"]

    def test_purchase_increments_downloads(self):
        from brynq.template_marketplace import purchase_template, get_template_stats

        t = _make_template()
        purchase_template(t["template_id"], "b1")
        purchase_template(t["template_id"], "b2")
        stats = get_template_stats(t["template_id"])
        assert stats["downloads"] == 2

    def test_purchase_paid_template_fee(self):
        from brynq.template_marketplace import purchase_template

        t = _make_template(title="Paid", price_credits=100)
        result = purchase_template(t["template_id"], "buyer-2")
        assert result["price"] == 100
        assert result["platform_fee"] == 15.0  # 15%
        assert result["seller_payout"] == 85.0

    def test_purchase_records_transaction(self):
        from brynq.template_marketplace import purchase_template, _all_transactions

        t = _make_template(price_credits=50)
        purchase_template(t["template_id"], "buyer-3")
        txs = _all_transactions()
        assert len(txs) == 1
        assert txs[0]["template_id"] == t["template_id"]
        assert txs[0]["buyer_id"] == "buyer-3"

    def test_purchase_nonexistent_template(self):
        from brynq.template_marketplace import purchase_template

        result = purchase_template("nonexistent", "buyer")
        assert "error" in result

    def test_purchase_updates_revenue(self):
        from brynq.template_marketplace import purchase_template, get_template_stats

        t = _make_template(price_credits=100)
        purchase_template(t["template_id"], "b1")
        stats = get_template_stats(t["template_id"])
        assert stats["revenue"] == 85.0  # 100 - 15% fee


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


class TestReviews:
    def test_review_template(self):
        from brynq.template_marketplace import review_template

        t = _make_template()
        review = review_template(t["template_id"], 4, "Solid template!", "rev-1")
        assert review["rating"] == 4
        assert review["comment"] == "Solid template!"
        assert review["reviewer_id"] == "rev-1"

    def test_review_updates_avg_rating(self):
        from brynq.template_marketplace import review_template, get_template_stats

        t = _make_template()
        review_template(t["template_id"], 5, "Perfect", "r1")
        review_template(t["template_id"], 3, "Okay", "r2")
        stats = get_template_stats(t["template_id"])
        assert stats["avg_rating"] == 4.0
        assert stats["review_count"] == 2

    def test_review_invalid_rating_low(self):
        t = _make_template()
        from brynq.template_marketplace import review_template

        with pytest.raises(ValueError, match="between 1 and 5"):
            review_template(t["template_id"], 0, "Bad", "r1")

    def test_review_invalid_rating_high(self):
        t = _make_template()
        from brynq.template_marketplace import review_template

        with pytest.raises(ValueError, match="between 1 and 5"):
            review_template(t["template_id"], 6, "Too good", "r1")

    def test_get_reviews(self):
        from brynq.template_marketplace import review_template, get_reviews

        t = _make_template()
        review_template(t["template_id"], 5, "A", "r1")
        review_template(t["template_id"], 3, "B", "r2")
        reviews = get_reviews(t["template_id"])
        assert len(reviews) == 2
        # Newest first
        assert reviews[0]["comment"] == "B"

    def test_review_nonexistent_template(self):
        from brynq.template_marketplace import review_template

        result = review_template("ghost", 5, "Nice", "r1")
        assert "error" in result

    def test_rating_distribution(self):
        from brynq.template_marketplace import review_template, get_template_stats

        t = _make_template()
        review_template(t["template_id"], 5, "a", "r1")
        review_template(t["template_id"], 5, "b", "r2")
        review_template(t["template_id"], 3, "c", "r3")
        stats = get_template_stats(t["template_id"])
        dist = stats["rating_distribution"]
        assert dist[5] == 2
        assert dist[3] == 1
        assert dist[1] == 0


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


class TestStats:
    def test_template_stats(self):
        from brynq.template_marketplace import get_template_stats

        t = _make_template(price_credits=50)
        stats = get_template_stats(t["template_id"])
        assert stats["template_id"] == t["template_id"]
        assert stats["downloads"] == 0
        assert stats["revenue"] == 0.0

    def test_template_stats_not_found(self):
        from brynq.template_marketplace import get_template_stats

        result = get_template_stats("missing")
        assert "error" in result

    def test_seller_stats(self):
        from brynq.template_marketplace import (
            get_seller_stats,
            purchase_template,
            review_template,
        )
        import brynq.template_marketplace as tm

        monkeypatch_id = "seller-abc"
        # We need to set the seller_id on the templates we create
        t1 = _make_template(title="T1", price_credits=100)
        t2 = _make_template(title="T2", price_credits=200)

        # The seller_id comes from SESSION_ID; read it
        sid = t1["seller_id"]

        purchase_template(t1["template_id"], "buyer")
        purchase_template(t2["template_id"], "buyer")
        review_template(t1["template_id"], 5, "Great", "buyer")
        review_template(t2["template_id"], 3, "OK", "buyer")

        stats = get_seller_stats(sid)
        assert stats["total_templates"] == 2
        assert stats["total_downloads"] == 2
        assert stats["total_revenue"] > 0
        assert stats["avg_rating"] == 4.0
        assert stats["total_reviews"] == 2

    def test_seller_stats_empty(self):
        from brynq.template_marketplace import get_seller_stats

        stats = get_seller_stats("nobody")
        assert stats["total_templates"] == 0
        assert stats["total_revenue"] == 0


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


class TestFeaturedTrending:
    def test_get_featured_by_rating(self):
        from brynq.template_marketplace import get_featured, review_template

        t1 = _make_template(title="High Rated")
        t2 = _make_template(title="Low Rated")
        review_template(t1["template_id"], 5, "Best", "r1")
        review_template(t2["template_id"], 2, "Meh", "r2")
        featured = get_featured(limit=5)
        assert len(featured) == 2
        assert featured[0]["title"] == "High Rated"

    def test_featured_flag_takes_priority(self):
        from brynq.template_marketplace import get_featured, review_template, _read_template, _write_template

        t1 = _make_template(title="Normal")
        t2 = _make_template(title="Featured")
        review_template(t1["template_id"], 5, "Perfect", "r1")

        # Manually flag t2 as featured
        tmpl = _read_template(t2["template_id"])
        tmpl["featured"] = True
        _write_template(tmpl)

        featured = get_featured(limit=5)
        assert featured[0]["title"] == "Featured"

    def test_get_trending(self):
        from brynq.template_marketplace import get_trending, purchase_template

        t1 = _make_template(title="Hot")
        t2 = _make_template(title="Cold")
        # Purchase t1 multiple times
        for i in range(5):
            purchase_template(t1["template_id"], f"buyer-{i}")
        purchase_template(t2["template_id"], "buyer-x")

        trending = get_trending(hours=24, limit=10)
        assert len(trending) >= 1
        assert trending[0]["title"] == "Hot"
        assert trending[0]["recent_downloads"] == 5

    def test_trending_empty(self):
        from brynq.template_marketplace import get_trending

        trending = get_trending()
        assert trending == []


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


class TestVersionManagement:
    def test_update_template(self):
        from brynq.template_marketplace import update_template

        t = _make_template()
        updated = update_template(
            t["template_id"],
            {"steps": [{"id": "s1", "action": "run_tests"}, {"id": "s2", "action": "deploy"}]},
            "Added deploy step",
        )
        assert updated["version"] == 2
        assert len(updated["versions"]) == 2
        assert updated["versions"][-1]["changelog"] == "Added deploy step"

    def test_multiple_versions(self):
        from brynq.template_marketplace import update_template, get_versions

        t = _make_template()
        update_template(t["template_id"], {"v": 2}, "v2 changes")
        update_template(t["template_id"], {"v": 3}, "v3 changes")
        versions = get_versions(t["template_id"])
        assert len(versions) == 3
        # Newest first
        assert versions[0]["version"] == 3
        assert versions[1]["version"] == 2
        assert versions[2]["version"] == 1

    def test_update_nonexistent(self):
        from brynq.template_marketplace import update_template

        result = update_template("ghost", {"data": 1}, "nope")
        assert "error" in result

    def test_get_versions_nonexistent(self):
        from brynq.template_marketplace import get_versions

        result = get_versions("ghost")
        assert "error" in result

    def test_purchase_returns_latest_version_data(self):
        from brynq.template_marketplace import update_template, purchase_template

        t = _make_template()
        new_data = {"steps": [{"id": "s1"}, {"id": "s2"}, {"id": "s3"}]}
        update_template(t["template_id"], new_data, "Added s2 and s3")
        result = purchase_template(t["template_id"], "buyer-late")
        assert result["template_data"] == new_data
