using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using PolyTrader.Core.Persistence.Ef;
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
using PolyTrader.Tests.TestSupport;
using PolyTraderSharp.Models;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>
    /// Feinkörnige Modell-Invarianten (Randfälle des Mappings): Dezimal-Präzision auf
    /// Geldfeldern, MaxLength auf Strings, manuell gesetzte Keys (ValueGeneratedNever),
    /// Value-Converter/Spaltentyp der JSON-Felder, Indizes.
    /// </summary>
    public class DbContextModelTests
    {
        private static CoreDbContext CoreCtx() =>
            new InMemoryContextFactory<CoreDbContext>(o => new CoreDbContext(o)).CreateDbContext();

        private static CopyTradingDbContext CtCtx() =>
            new InMemoryContextFactory<CopyTradingDbContext>(o => new CopyTradingDbContext(o)).CreateDbContext();

        private static IProperty Prop(DbContext ctx, Type clr, string name) =>
            ctx.Model.FindEntityType(clr)!.FindProperty(name)!;

        // GetColumnType() wirft unter dem InMemory-Provider (kein RelationalTypeMapping);
        // die HasColumnType-Annotation lässt sich aber providerunabhängig auslesen.
        private static string? ColumnType(IProperty p) =>
            p.FindAnnotation("Relational:ColumnType")?.Value as string;

        [Theory]
        [InlineData(typeof(AccountState), nameof(AccountState.TotalBalance))]
        [InlineData(typeof(AccountState), nameof(AccountState.AvailableBalance))]
        [InlineData(typeof(Position), nameof(Position.EntryPrice))]
        [InlineData(typeof(TradeRecord), nameof(TradeRecord.RealizedPnl))]
        public void Core_money_fields_have_precision_18_6(Type clr, string name)
        {
            using var ctx = CoreCtx();
            var p = Prop(ctx, clr, name);
            Assert.Equal(18, p.GetPrecision());
            Assert.Equal(6, p.GetScale());
        }

        [Theory]
        [InlineData(nameof(CopyTradingAccountSettings.PerMarketLimit))]
        [InlineData(nameof(CopyTradingAccountSettings.PerMasterLimit))]
        [InlineData(nameof(CopyTradingAccountSettings.perMaxTimeNone))]
        [InlineData(nameof(CopyTradingAccountSettings.SellFloorPct))]
        [InlineData(nameof(CopyTradingAccountSettings.MaxSpreadPct))]
        [InlineData(nameof(CopyTradingAccountSettings.MinSellRatioPct))]
        public void Settings_money_fields_have_precision_18_6(string name)
        {
            using var ctx = CtCtx();
            var p = Prop(ctx, typeof(CopyTradingAccountSettings), name);
            Assert.Equal(18, p.GetPrecision());
            Assert.Equal(6, p.GetScale());
        }

        [Theory]
        [InlineData(typeof(AccountState), nameof(AccountState.WalletAddress), 128)]
        [InlineData(typeof(AccountState), nameof(AccountState.Name), 200)]
        [InlineData(typeof(MarketData), nameof(MarketData.Id), 120)]
        public void Core_string_fields_have_expected_max_length(Type clr, string name, int expected)
        {
            using var ctx = CoreCtx();
            Assert.Equal(expected, Prop(ctx, clr, name).GetMaxLength());
        }

        [Fact]
        public void TrackedTrader_category_has_max_length_64()
        {
            using var ctx = CtCtx();
            Assert.Equal(64, Prop(ctx, typeof(TrackedTrader), nameof(TrackedTrader.Category)).GetMaxLength());
        }

        [Theory]
        [InlineData(typeof(AccountState), nameof(AccountState.AccountId))]
        public void Core_manual_keys_are_not_store_generated(Type clr, string name)
        {
            using var ctx = CoreCtx();
            Assert.Equal(ValueGenerated.Never, Prop(ctx, clr, name).ValueGenerated);
        }

        [Theory]
        [InlineData(typeof(TrackedTrader), nameof(TrackedTrader.Id))]
        [InlineData(typeof(ClosedTrade), nameof(ClosedTrade.TradeId))]
        [InlineData(typeof(CopyTradingAccountSettings), nameof(CopyTradingAccountSettings.AccountId))]
        public void Module_manual_keys_are_not_store_generated(Type clr, string name)
        {
            using var ctx = CtCtx();
            Assert.Equal(ValueGenerated.Never, Prop(ctx, clr, name).ValueGenerated);
        }

        [Fact]
        public void AssignedAccountIds_has_value_converter_and_text_column()
        {
            using var ctx = CtCtx();
            var p = Prop(ctx, typeof(TrackedTrader), nameof(TrackedTrader.AssignedAccountIds));
            Assert.NotNull(p.GetValueConverter());
            Assert.Equal("text", ColumnType(p));
        }

        [Theory]
        [InlineData(nameof(MarketData.ClobTokenIds))]
        [InlineData(nameof(MarketData.Outcomes))]
        public void Market_json_columns_are_text(string name)
        {
            using var ctx = CoreCtx();
            Assert.Equal("text", ColumnType(Prop(ctx, typeof(MarketData), name)));
        }

        [Fact]
        public void TradeRecord_is_indexed_on_closed_at()
        {
            using var ctx = CoreCtx();
            var indexed = ctx.Model.FindEntityType(typeof(TradeRecord))!
                .GetIndexes()
                .SelectMany(i => i.Properties.Select(p => p.Name));
            Assert.Contains(nameof(TradeRecord.ClosedAt), indexed);
        }

        [Fact]
        public void MasterTraderHistory_is_indexed_on_trader_and_closed_at()
        {
            using var ctx = CtCtx();
            var indexed = ctx.Model.FindEntityType(typeof(MasterTraderHistoryRecord))!
                .GetIndexes()
                .SelectMany(i => i.Properties.Select(p => p.Name))
                .ToList();
            Assert.Contains(nameof(MasterTraderHistoryRecord.TraderId), indexed);
            Assert.Contains(nameof(MasterTraderHistoryRecord.ClosedAt), indexed);
        }
    }
}
