using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PolyTrader.Modules.Supervisor.Agent;
using PolyTrader.Modules.Supervisor.Counterfactual;
using PolyTrader.Modules.Supervisor.Persistence;
using PolyTrader.Tests.Fakes;
using PolyTrader.Tests.TestSupport;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>Sicherheitsnetz für S-3: Counterfactual-Job (Rejects vs. Marktausgang) und Tagesbericht.</summary>
    public class SupervisorS3Tests
    {
        // ----- CounterfactualMath (pur) -----

        [Theory]
        [InlineData(0.95, true, 0.05)]    // Gewinner: 1 - 0.95
        [InlineData(0.95, false, -0.95)]  // Verlierer: -Preis
        [InlineData(0.40, true, 0.60)]
        public void PnlPerShare_winner_and_loser(double price, bool winner, double expected)
        {
            Assert.Equal((decimal)expected, CounterfactualMath.PnlPerShare((decimal)price, winner));
        }

        // ----- CounterfactualJob -----

        private sealed class FakeResolutionSource : ICounterfactualResolutionSource
        {
            public Dictionary<string, (bool resolved, bool winner)> ByToken { get; } = new();
            public int Calls { get; private set; }
            public Task<(bool isResolved, bool isWinner)> CheckAsync(string slug, string tokenId, CancellationToken ct)
            {
                Calls++;
                return Task.FromResult(ByToken.TryGetValue(tokenId, out var r) ? r : (false, false));
            }
        }

        private static DecisionRecord RejectedBuy(long id, string token, decimal price, DateTime endDate) => new()
        {
            Id = id, SignalId = "sig-" + id, ModuleName = "CopyTrading", AccountId = 1,
            TokenId = token, MarketSlug = "slug-" + token, MarketQuestion = "Q-" + token, Side = "BUY",
            SignalPrice = price, MarketEndDate = endDate,
            Decision = TradeDecision.Rejected, Reason = DecisionReason.MaxBuyPriceExceeded
        };

        private static (CounterfactualJob job, FakeDecisionJournal journal, EfSupervisorCounterfactualRepository repo, FakeResolutionSource res) BuildJob()
        {
            var journal = new FakeDecisionJournal();
            var factory = new InMemoryContextFactory<SupervisorDbContext>(o => new SupervisorDbContext(o));
            var repo = new EfSupervisorCounterfactualRepository(factory);
            var res = new FakeResolutionSource();
            var job = new CounterfactualJob(journal, repo, res, new TerminalLogger());
            return (job, journal, repo, res);
        }

        [Fact]
        public async Task Job_evaluates_resolved_rejects_and_skips_unresolved()
        {
            var (job, journal, repo, res) = BuildJob();
            var past = DateTime.UtcNow.AddDays(-1);
            journal.Written.Add(RejectedBuy(1, "a", 0.95m, past));   // aufgelöst: Gewinner
            journal.Written.Add(RejectedBuy(2, "b", 0.60m, past));   // aufgelöst: Verlierer
            journal.Written.Add(RejectedBuy(3, "c", 0.90m, past));   // noch NICHT aufgelöst
            journal.Written.Add(RejectedBuy(4, "d", 0.90m, DateTime.UtcNow.AddDays(2)));   // Markt läuft noch
            res.ByToken["a"] = (true, true);
            res.ByToken["b"] = (true, false);

            int added = await job.CheckOnceAsync(CancellationToken.None);

            Assert.Equal(2, added);
            var rows = repo.GetRecent(10);
            Assert.Equal(2, rows.Count);
            var a = rows.Single(r => r.TokenId == "a");
            Assert.True(a.IsWinner);
            Assert.Equal(0.05m, a.HypotheticalPnlPerShare);
            Assert.Equal("MaxBuyPriceExceeded", a.Reason);
            var b = rows.Single(r => r.TokenId == "b");
            Assert.False(b.IsWinner);
            Assert.Equal(-0.60m, b.HypotheticalPnlPerShare);
        }

        [Fact]
        public async Task Job_is_idempotent_per_decision()
        {
            var (job, journal, repo, res) = BuildJob();
            journal.Written.Add(RejectedBuy(1, "a", 0.95m, DateTime.UtcNow.AddDays(-1)));
            res.ByToken["a"] = (true, true);

            Assert.Equal(1, await job.CheckOnceAsync(CancellationToken.None));
            Assert.Equal(0, await job.CheckOnceAsync(CancellationToken.None));   // schon ausgewertet
            Assert.Single(repo.GetRecent(10));
        }

        // ----- DailyReportService -----

        private sealed class OneShotChatClient : IChatCompletionClient
        {
            public Task<ChatResponse> CompleteAsync(string model, IReadOnlyList<ChatMessage> messages,
                IReadOnlyList<SupervisorTool> tools, CancellationToken ct)
                => Task.FromResult(new ChatResponse { Content = "Alles im grünen Bereich." });
        }

        [Fact]
        public async Task DailyReport_runs_agent_saves_report_and_sends()
        {
            Environment.SetEnvironmentVariable("POLYTRADER_OPENROUTER_KEY", "test-key");
            try
            {
                var factory = new InMemoryContextFactory<SupervisorDbContext>(o => new SupervisorDbContext(o));
                var reports = new EfSupervisorReportRepository(factory);
                var agent = new SupervisorAgent(new OneShotChatClient(), new SupervisorToolRegistry());
                string? sent = null;
                var svc = new PolyTrader.Modules.Supervisor.Services.DailyReportService(
                    agent, reports, msg => { sent = msg; return Task.CompletedTask; }, new TerminalLogger());

                await svc.RunOnceAsync(CancellationToken.None);

                Assert.NotNull(sent);
                Assert.Contains("Alles im grünen Bereich", sent);
                var saved = reports.GetRecent(5);
                Assert.Single(saved);
                Assert.Equal("Tagesbericht", saved[0].Profile);
            }
            finally
            {
                Environment.SetEnvironmentVariable("POLYTRADER_OPENROUTER_KEY", null);
            }
        }

        [Fact]
        public async Task DailyReport_skips_without_api_key()
        {
            Environment.SetEnvironmentVariable("POLYTRADER_OPENROUTER_KEY", null);
            var factory = new InMemoryContextFactory<SupervisorDbContext>(o => new SupervisorDbContext(o));
            var reports = new EfSupervisorReportRepository(factory);
            var agent = new SupervisorAgent(new OneShotChatClient(), new SupervisorToolRegistry());
            bool sent = false;
            var svc = new PolyTrader.Modules.Supervisor.Services.DailyReportService(
                agent, reports, _ => { sent = true; return Task.CompletedTask; }, new TerminalLogger());

            await svc.RunOnceAsync(CancellationToken.None);

            Assert.False(sent);
            Assert.Empty(reports.GetRecent(5));
        }
    }
}
