using System;
using System.Linq;
using PolyTrader.Modules.ResolutionFarming.Models;
using PolyTrader.Modules.ResolutionFarming.Persistence.Ef;
using PolyTrader.Tests.TestSupport;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>EF-InMemory-Tests der ResolutionFarming-Repositories (rf_-Tabellen).</summary>
    public class RfRepositoryTests
    {
        private static InMemoryContextFactory<ResolutionFarmingDbContext> Factory() =>
            new(o => new ResolutionFarmingDbContext(o));

        // ----- Settings -----

        [Fact]
        public void Settings_upsert_inserts_then_updates()
        {
            var f = Factory();
            var repo = new EfRfSettingsRepository(f);
            repo.Upsert(new RfSettings { AccountId = 1, MaxPerMarketUsd = 25m });
            repo.Upsert(new RfSettings { AccountId = 1, MaxPerMarketUsd = 50m });

            Assert.Single(repo.GetAll());
            Assert.Equal(50m, repo.Get(1)!.MaxPerMarketUsd);
            Assert.Null(repo.Get(99));
        }

        // ----- Candidates -----

        [Fact]
        public void Candidates_getrecent_orders_desc_and_limits()
        {
            var f = Factory();
            var repo = new EfRfCandidateRepository(f);
            var t0 = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc);
            for (int i = 0; i < 5; i++)
                repo.Insert(new RfCandidate { AccountId = 1, TokenId = "t" + i, ScannedAt = t0.AddMinutes(i) });
            repo.Insert(new RfCandidate { AccountId = 2, TokenId = "other", ScannedAt = t0.AddMinutes(99) });

            var recent = repo.GetRecent(1, 3);
            Assert.Equal(3, recent.Count);
            Assert.Equal("t4", recent[0].TokenId);   // neuester zuerst
            Assert.DoesNotContain(recent, c => c.AccountId == 2);
        }

        [Fact]
        public void Candidate_id_is_db_generated()
        {
            var f = Factory();
            var repo = new EfRfCandidateRepository(f);
            repo.Insert(new RfCandidate { AccountId = 1, TokenId = "a" });
            repo.Insert(new RfCandidate { AccountId = 1, TokenId = "b" });

            var ids = repo.GetRecent(1, 10).Select(c => c.Id).ToList();
            Assert.Equal(2, ids.Distinct().Count());
            Assert.DoesNotContain(0L, ids);   // Autoincrement, keine code-vergebene 0
        }

        // ----- Positions -----

        [Fact]
        public void Positions_upsert_find_delete_and_exposure()
        {
            var f = Factory();
            var repo = new EfRfPositionRepository(f);
            var now = DateTime.UtcNow;
            repo.Upsert(new RfPosition { AccountId = 1, TokenId = "a", AmountUsd = 10m, OpenedAt = now });
            repo.Upsert(new RfPosition { AccountId = 1, TokenId = "b", AmountUsd = 15m, OpenedAt = now });
            repo.Upsert(new RfPosition { AccountId = 2, TokenId = "c", AmountUsd = 99m, OpenedAt = now });

            Assert.Equal(2, repo.GetOpen(1).Count);
            Assert.Equal(3, repo.GetAllOpen().Count);
            Assert.Equal(15m, repo.Find(1, "b")!.AmountUsd);

            // Update über denselben zusammengesetzten Schlüssel
            repo.Upsert(new RfPosition { AccountId = 1, TokenId = "a", AmountUsd = 12m, OpenedAt = now });
            Assert.Equal(12m, repo.Find(1, "a")!.AmountUsd);
            Assert.Equal(2, repo.GetOpen(1).Count);   // kein Duplikat

            repo.Delete(1, "a");
            Assert.Null(repo.Find(1, "a"));
        }

        [Fact]
        public void Positions_countopenedsince_respects_account_and_time()
        {
            var f = Factory();
            var repo = new EfRfPositionRepository(f);
            var today = DateTime.UtcNow;
            repo.Upsert(new RfPosition { AccountId = 1, TokenId = "a", OpenedAt = today });
            repo.Upsert(new RfPosition { AccountId = 1, TokenId = "b", OpenedAt = today.AddDays(-2) });
            repo.Upsert(new RfPosition { AccountId = 2, TokenId = "c", OpenedAt = today });

            Assert.Equal(1, repo.CountOpenedSince(1, today.AddHours(-1)));
        }

        // ----- ClosedTrades -----

        [Fact]
        public void ClosedTrades_insert_find_and_pnl_sum()
        {
            var f = Factory();
            var repo = new EfRfClosedTradeRepository(f);
            var since = new DateTime(2026, 7, 9, 0, 0, 0, DateTimeKind.Utc);
            repo.Insert(new RfClosedTrade { AccountId = 1, TokenId = "a", RealizedPnl = 5m, ClosedAt = since.AddHours(1) });
            repo.Insert(new RfClosedTrade { AccountId = 1, TokenId = "b", RealizedPnl = -8m, ClosedAt = since.AddHours(2) });
            repo.Insert(new RfClosedTrade { AccountId = 1, TokenId = "c", RealizedPnl = 100m, ClosedAt = since.AddDays(-1) }); // vor dem Fenster
            repo.Insert(new RfClosedTrade { AccountId = 2, TokenId = "d", RealizedPnl = 50m, ClosedAt = since.AddHours(1) });

            Assert.Equal(-3m, repo.RealizedPnlSince(1, since));   // 5 - 8, ohne 100 (zu alt), ohne Account 2
            Assert.Equal(2, repo.Find(t => t.RealizedPnl > 0 && t.AccountId == 1).Count);   // a (5) + c (100)
        }
    }
}
