using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Persistence;
using PolyTrader.Core.Persistence.Ef;
using PolyTrader.Core.Platforms;
using PolyTrader.Core.Platforms.PolymarketGlobal;
using PolyTrader.Core.Trading;
using PolyTrader.Tests.Fakes;
using PolyTrader.Tests.TestSupport;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
using Xunit;
namespace PolyTrader.Tests
{
///
/// #623 Plattform-Fundament: Kennungen, Registry, und dass der Global-Adapter den bestehenden CLOB-Client
/// genau so aufruft, wie die Module es vor #623 selbst getan haben.
///
public class PlatformTests
{
// ── PlatformId ─────────────────────────────────────────────────────────
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Parse_of_missing_value_is_polymarket_global(string? stored)
{
Assert.Equal(PlatformId.PolymarketGlobal, PlatformId.Parse(stored));
}
[Fact]
public void Parse_normalizes_case_and_whitespace()
{
Assert.Equal(PlatformId.PolymarketUs, PlatformId.Parse(" Polymarket-US "));
}
[Fact]
public void Stored_values_are_stable()
{
// Diese Werte stehen in der Datenbank. Ändert sich einer, verlieren Bestandskonten ihre Plattform.
Assert.Equal("polymarket-global", PlatformId.PolymarketGlobal.Value);
Assert.Equal("polymarket-us", PlatformId.PolymarketUs.Value);
Assert.Equal("kalshi", PlatformId.Kalshi.Value);
}
[Fact]
public void New_accounts_and_markets_default_to_polymarket_global()
{
Assert.Equal(PlatformId.PolymarketGlobalValue, new AccountState().Platform);
Assert.Equal(PlatformId.PolymarketGlobalValue, new MarketData().Platform);
}
// ── InstrumentRef ─────────────────────────────────────────────────────
[Fact]
public void Instruments_are_equal_by_platform_and_key_only()
{
var a = InstrumentRef.PolymarketGlobal("123", "0xcond", "Yes");
var b = InstrumentRef.PolymarketGlobal("123");
Assert.Equal(a, b);
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
public void Same_key_on_different_platforms_is_a_different_instrument()
{
Assert.NotEqual(
new InstrumentRef(PlatformId.PolymarketGlobal, "x"),
new InstrumentRef(PlatformId.PolymarketUs, "x"));
}
[Fact]
public void Empty_instrument_key_is_rejected()
{
Assert.Throws(() => new InstrumentRef(PlatformId.PolymarketGlobal, " "));
}
// ── Registry ──────────────────────────────────────────────────────────
[Fact]
public void Registry_resolves_adapter_for_account_platform()
{
var global = NewGlobalAdapter(new FakeClobClient(), new StubMarketRepository());
var registry = new PlatformRegistry(new[] { global });
Assert.Same(global, registry.ForAccount(new AccountState()));
Assert.Same(global, registry.ForAccount(new AccountState { Platform = "" }));
Assert.Equal(new[] { PlatformId.PolymarketGlobal }, registry.Available);
}
[Fact]
public void Registry_names_missing_platform_instead_of_returning_null()
{
var registry = new PlatformRegistry(new[] { NewGlobalAdapter(new FakeClobClient(), new StubMarketRepository()) });
var ex = Assert.Throws(() => registry.Get(PlatformId.PolymarketUs));
Assert.Contains("Polymarket US", ex.Message);
Assert.False(registry.TryGet(PlatformId.Kalshi, out _));
}
[Fact]
public void Registry_refuses_two_adapters_for_one_platform()
{
var a = NewGlobalAdapter(new FakeClobClient(), new StubMarketRepository());
var b = NewGlobalAdapter(new FakeClobClient(), new StubMarketRepository());
Assert.Throws(() => new PlatformRegistry(new[] { a, b }));
}
[Fact]
public void AddPlatforms_wires_global_adapter_from_the_app_singletons()
{
// Wie Program.cs: API-Dienst, CLOB-Client und Persistenz sind registriert, CopyTrading (und damit
// dessen IClobClient-Registrierung) bewusst NICHT – der Adapter muss trotzdem auflösbar sein.
var logger = new TerminalLogger();
var services = new ServiceCollection();
services.AddSingleton(new PolymarketApiService(logger, new HttpClient()));
services.AddSingleton(new PolymarketClobClient(logger, new HttpClient()));
services.AddSingleton(new StubMarketRepository());
services.AddPlatforms();
using var sp = services.BuildServiceProvider();
var registry = sp.GetRequiredService();
var adapter = registry.ForAccount(new AccountState());
Assert.IsType(adapter);
Assert.IsType(adapter.Orders);
Assert.True(adapter.Capabilities.PublicTraderActivity);
Assert.False(adapter.Capabilities.PostOnlyOrders);
}
// ── Global-Gateway: derselbe CLOB-Aufruf wie vorher ───────────────────
[Fact]
public async Task Buy_by_notional_passes_amount_price_negrisk_and_market_fee_through()
{
var clob = new FakeClobClient();
var markets = new StubMarketRepository(new MarketData
{
Id = "m1", ClobTokenIds = "[\"tok-yes\",\"tok-no\"]", NegRisk = true, TakerFeeBps = 180, Category = "Sports"
});
var gateway = new GlobalOrderGateway(clob, markets);
var result = await gateway.PlaceAsync(new AccountState(), new OrderRequest
{
Instrument = InstrumentRef.PolymarketGlobal("tok-yes"),
Side = OrderSide.Buy,
LimitPrice = 0.42m,
NotionalUsd = 25m,
TimeInForce = TimeInForce.FillOrKill
});
Assert.True(result.IsAccepted);
var placed = Assert.Single(clob.Placed);
Assert.Equal(new FakeClobClient.PlacedOrder("tok-yes", "BUY", 25m, 0.42m, "FOK", IsNegRisk: true, FeeBps: 180), placed);
}
[Fact]
public async Task Sell_by_quantity_converts_to_amount_like_the_sell_ladder()
{
// Die SELL-Leiter übergibt pos.Size * Preis als Betrag – das Gateway muss dasselbe senden.
var clob = new FakeClobClient();
var gateway = new GlobalOrderGateway(clob, new StubMarketRepository());
await gateway.PlaceAsync(new AccountState(), new OrderRequest
{
Instrument = InstrumentRef.PolymarketGlobal("tok"),
Side = OrderSide.Sell,
LimitPrice = 0.8m,
Quantity = 50m,
TimeInForce = TimeInForce.GoodTillCancel
});
var placed = Assert.Single(clob.Placed);
Assert.Equal("SELL", placed.Side);
Assert.Equal(40m, placed.Usdc);
Assert.Equal("GTC", placed.OrderType);
}
[Fact]
public async Task Unknown_market_uses_conservative_fee_fallback_and_no_negrisk()
{
var clob = new FakeClobClient();
var gateway = new GlobalOrderGateway(clob, new StubMarketRepository());
await gateway.PlaceAsync(new AccountState(), Buy("tok", 0.5m, 10m));
var placed = Assert.Single(clob.Placed);
Assert.False(placed.IsNegRisk);
Assert.Equal(FeeModel.FallbackBps(null), placed.FeeBps);
}
public static IEnumerable