"""Tests for Polymarket data provider and broker adapter."""

from __future__ import annotations

import json
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch, PropertyMock

import pytest

from cpz.data.providers.polymarket import PolymarketProvider
from cpz.data.models import Bar, Quote, TimeFrame
from cpz.data.client import DataClient


# ---------------------------------------------------------------------------
# Data Provider Tests
# ---------------------------------------------------------------------------


class TestPolymarketProvider:
    """Test the Polymarket data provider (free, no auth)."""

    def setup_method(self):
        self.provider = PolymarketProvider()

    def test_provider_attributes(self):
        assert self.provider.name == "polymarket"
        assert "prediction_markets" in self.provider.supported_assets

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_events(self, mock_gamma):
        mock_gamma.return_value = [
            {"id": "1", "title": "Test Event", "markets": []},
            {"id": "2", "title": "Another Event", "markets": []},
        ]
        events = self.provider.get_events(limit=10, tag="politics")
        assert len(events) == 2
        assert events[0]["title"] == "Test Event"
        mock_gamma.assert_called_once()
        call_args = mock_gamma.call_args
        assert call_args[0][0] == "/events"

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_markets(self, mock_gamma):
        mock_gamma.return_value = [
            {
                "id": "m1",
                "question": "Will X happen?",
                "outcomePrices": json.dumps([0.65, 0.35]),
                "volume": 1000000,
            }
        ]
        markets = self.provider.get_markets(tag="crypto", limit=5)
        assert len(markets) == 1
        assert markets[0]["question"] == "Will X happen?"

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_search(self, mock_gamma):
        mock_gamma.return_value = {"events": [{"title": "Iran regime"}]}
        results = self.provider.search("Iran", limit=5)
        assert len(results) == 1
        assert results[0]["title"] == "Iran regime"

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_market_price(self, mock_gamma):
        mock_gamma.return_value = [
            {
                "id": "m1",
                "condition_id": "0xabc",
                "outcomePrices": json.dumps([0.72, 0.28]),
            }
        ]
        prices = self.provider.get_market_price("0xabc")
        assert prices is not None
        assert abs(prices["yes"] - 0.72) < 0.01
        assert abs(prices["no"] - 0.28) < 0.01

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_market_price_not_found(self, mock_gamma):
        mock_gamma.return_value = []
        prices = self.provider.get_market_price("nonexistent")
        assert prices is None

    @patch("cpz.data.providers.polymarket.PolymarketProvider._clob")
    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_bars(self, mock_gamma, mock_clob):
        mock_gamma.return_value = [
            {
                "id": "m1",
                "condition_id": "0xabc",
                "clob_token_ids": json.dumps(["token123"]),
            }
        ]
        now = int(datetime.utcnow().timestamp())
        mock_clob.return_value = {
            "history": [
                {"t": now - 86400, "p": 0.60},
                {"t": now - 43200, "p": 0.65},
                {"t": now, "p": 0.70},
            ]
        }

        bars = self.provider.get_bars("0xabc", timeframe="1D")
        assert len(bars) == 3
        assert isinstance(bars[0], Bar)
        assert bars[0].close == 0.60
        assert bars[2].close == 0.70

    @patch("cpz.data.providers.polymarket.PolymarketProvider._clob")
    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_bars_with_limit(self, mock_gamma, mock_clob):
        mock_gamma.return_value = [
            {"clob_token_ids": json.dumps(["token123"])}
        ]
        now = int(datetime.utcnow().timestamp())
        mock_clob.return_value = {
            "history": [
                {"t": now - 86400 * i, "p": 0.5 + i * 0.01}
                for i in range(20)
            ]
        }
        bars = self.provider.get_bars("test-slug", limit=5)
        assert len(bars) == 5

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_bars_no_market(self, mock_gamma):
        mock_gamma.return_value = []
        bars = self.provider.get_bars("nonexistent")
        assert bars == []

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_quote(self, mock_gamma):
        mock_gamma.return_value = [
            {
                "condition_id": "0xabc",
                "outcomePrices": json.dumps([0.55, 0.45]),
            }
        ]
        quote = self.provider.get_quote("0xabc")
        assert quote is not None
        assert isinstance(quote, Quote)
        assert abs(quote.bid - 0.55) < 0.01

    @patch("cpz.data.providers.polymarket.PolymarketProvider._gamma")
    def test_get_quotes(self, mock_gamma):
        mock_gamma.side_effect = [
            [{"condition_id": "0xabc", "outcomePrices": json.dumps([0.6, 0.4])}],
            [{"condition_id": "0xdef", "outcomePrices": json.dumps([0.3, 0.7])}],
        ]
        quotes = self.provider.get_quotes(["0xabc", "0xdef"])
        assert len(quotes) == 2


# ---------------------------------------------------------------------------
# DataClient Namespace Tests
# ---------------------------------------------------------------------------


class TestPolymarketNamespace:
    """Test the Polymarket namespace on DataClient."""

    def test_polymarket_property_exists(self):
        dc = DataClient()
        assert hasattr(dc, "polymarket")
        ns = dc.polymarket
        assert hasattr(ns, "get_events")
        assert hasattr(ns, "get_markets")
        assert hasattr(ns, "search")
        assert hasattr(ns, "get_bars")
        assert hasattr(ns, "get_quote")

    def test_convenience_methods_exist(self):
        dc = DataClient()
        assert hasattr(dc, "polymarket_events")
        assert hasattr(dc, "polymarket_markets")
        assert hasattr(dc, "polymarket_prices")

    @patch("cpz.data.providers.polymarket.PolymarketProvider.get_events")
    def test_polymarket_events(self, mock_get_events):
        mock_get_events.return_value = [{"title": "Test"}]
        dc = DataClient()
        events = dc.polymarket_events(tag="politics", limit=10)
        assert len(events) == 1
        mock_get_events.assert_called_once()

    @patch("cpz.data.providers.polymarket.PolymarketProvider.get_markets")
    def test_polymarket_markets(self, mock_get_markets):
        mock_get_markets.return_value = [{"question": "Test?"}]
        dc = DataClient()
        markets = dc.polymarket_markets(tag="crypto")
        assert len(markets) == 1
        mock_get_markets.assert_called_once()

    @patch("cpz.data.providers.polymarket.PolymarketProvider.get_bars")
    def test_polymarket_prices(self, mock_get_bars):
        mock_get_bars.return_value = [
            Bar(symbol="test", timestamp=datetime.utcnow(), open=0.5, high=0.5, low=0.5, close=0.5, volume=0)
        ]
        dc = DataClient()
        bars = dc.polymarket_prices("test-slug", timeframe="1D")
        assert len(bars) == 1
        assert isinstance(bars[0], Bar)


# ---------------------------------------------------------------------------
# Broker Adapter Tests
# ---------------------------------------------------------------------------


class TestPolymarketAdapter:
    """Test the Polymarket broker adapter."""

    def test_adapter_imports(self):
        from cpz.execution.polymarket.adapter import PolymarketAdapter
        assert PolymarketAdapter is not None

    def test_router_registers_polymarket(self):
        from cpz.execution.router import BrokerRouter, BROKER_POLYMARKET
        router = BrokerRouter.default()
        assert BROKER_POLYMARKET in router.list_brokers()

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_get_account(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "success": True,
            "data": {
                "wallet_address": "0x1234abcd",
                "buying_power": "500.00",
                "equity": "1200.00",
                "cash": "500.00",
            },
        }
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        adapter = PolymarketAdapter(
            cpz_api_key="test_key",
            account_id="test_account",
        )
        account = adapter.get_account()
        assert account.id == "0x1234abcd"
        assert account.buying_power == 500.0
        assert account.equity == 1200.0

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_get_positions(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "success": True,
            "data": [
                {
                    "symbol": "Will Iran regime fall?",
                    "qty": "100",
                    "avg_entry_price": "0.40",
                    "current_price": "0.55",
                    "market_value": "55.00",
                    "unrealized_pl": "15.00",
                    "side": "long",
                }
            ],
        }
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        adapter = PolymarketAdapter(cpz_api_key="test", account_id="test")
        positions = adapter.get_positions()
        assert len(positions) == 1
        assert positions[0].symbol == "Will Iran regime fall?"
        assert positions[0].qty == 100.0
        assert positions[0].market_value == 55.0

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_get_orders(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "success": True,
            "data": [
                {
                    "id": "order-1",
                    "symbol": "Test Market",
                    "side": "buy",
                    "qty": "50",
                    "filled_qty": "50",
                    "type": "limit",
                    "status": "filled",
                    "limit_price": "0.60",
                    "created_at": "2026-02-28T12:00:00Z",
                }
            ],
        }
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        adapter = PolymarketAdapter(cpz_api_key="test", account_id="test")
        orders = adapter.get_orders()
        assert len(orders) == 1
        assert orders[0].id == "order-1"
        assert orders[0].status == "filled"

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_submit_order(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {
            "success": True,
            "data": {"id": "new-order-1", "status": "new"},
        }
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        from cpz.execution.models import OrderSubmitRequest
        from cpz.execution.enums import OrderSide, OrderType, TimeInForce

        adapter = PolymarketAdapter(cpz_api_key="test", account_id="test")
        order = adapter.submit_order(
            OrderSubmitRequest(
                symbol="token-123",
                side=OrderSide.BUY,
                qty=100,
                order_type=OrderType.LIMIT,
                limit_price=0.65,
                time_in_force=TimeInForce.GTC,
                strategy_id="test-strategy",
            )
        )
        assert order.id == "new-order-1"
        assert order.status == "new"
        assert order.qty == 100

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_cancel_order(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"success": True, "data": {}}
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        adapter = PolymarketAdapter(cpz_api_key="test", account_id="test")
        adapter.cancel_order("order-to-cancel")
        mock_post.assert_called_once()

    @patch("cpz.execution.polymarket.adapter.requests.post")
    def test_error_handling(self, mock_post):
        mock_resp = MagicMock()
        mock_resp.status_code = 401
        mock_resp.text = "Unauthorized"
        mock_resp.json.return_value = {"error": True, "message": "Unauthorized"}
        mock_post.return_value = mock_resp

        from cpz.execution.polymarket.adapter import PolymarketAdapter
        adapter = PolymarketAdapter(cpz_api_key="bad_key", account_id="test")
        with pytest.raises(Exception, match="Polymarket error"):
            adapter.get_account()


# ---------------------------------------------------------------------------
# Integration / Import Tests
# ---------------------------------------------------------------------------


class TestPolymarketIntegration:
    """Verify Polymarket is properly wired into the SDK."""

    def test_broker_constant_exported(self):
        from cpz import BROKER_POLYMARKET
        assert BROKER_POLYMARKET == "polymarket"

    def test_polymarket_in_all(self):
        import cpz
        assert "BROKER_POLYMARKET" in cpz.__all__

    def test_version_bumped(self):
        import cpz
        assert cpz.__version__ == "3.0.0"

    def test_data_client_has_polymarket(self):
        dc = DataClient()
        assert hasattr(dc, "polymarket")
        assert hasattr(dc, "polymarket_events")
        assert hasattr(dc, "polymarket_markets")
        assert hasattr(dc, "polymarket_prices")
