using System;
using System.Collections.Generic;
using System.Linq;
using PolyTrader.Modules.Accounting.Logic;
using PolyTrader.Modules.Accounting.Models;
using Xunit;

namespace PolyTrader.Tests
{
    /// <summary>Sicherheitsnetz für die neutrale Abrechnung (A-2): Periodenaggregation, FX, CSV.</summary>
    public class AccountingReportTests
    {
        private static readonly DateTime Jun = new(2026, 6, 15, 12, 0, 0, DateTimeKind.Utc);
        private static readonly DateTime Jul = new(2026, 7, 10, 12, 0, 0, DateTimeKind.Utc);

        private static LedgerEntry E(LedgerEventType type, decimal net, DateTime ts,
            decimal gross = 0m, decimal fee = 0m) => new()
        {
            AccountId = 1, EventType = type, Timestamp = ts, NetUsdc = net,
            GrossUsdc = gross == 0m ? Math.Abs(net) : gross, FeeUsdc = fee
        };

        // ---------------- AccountingEngine ----------------

        [Fact]
        public void Statement_aggregates_period_and_carries_opening_balance()
        {
            var entries = new List<LedgerEntry>
            {
                // VOR dem Zeitraum → nur Anfangssaldo
                E(LedgerEventType.Deposit, 1000m, new DateTime(2026, 5, 1, 0, 0, 0, DateTimeKind.Utc), gross: 1000m),
                // im Zeitraum (Juli)
                E(LedgerEventType.TradeBuy, -100m, Jul, gross: 100m, fee: 1m),
                E(LedgerEventType.TradeSell, 130m, Jul, gross: 130m, fee: 1m),
                E(LedgerEventType.Reward, 5m, Jul, gross: 5m),
                E(LedgerEventType.Deposit, 200m, Jul, gross: 200m),
                E(LedgerEventType.Withdrawal, -50m, Jul, gross: 50m),
            };

            var from = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc);
            var to = new DateTime(2026, 7, 31, 23, 59, 59, DateTimeKind.Utc);
            var s = AccountingEngine.BuildStatement(entries, from, to, 1);

            Assert.Equal(1000m, s.OpeningBalanceUsdc);
            Assert.Equal(200m, s.Deposits);
            Assert.Equal(50m, s.Withdrawals);
            Assert.Equal(230m, s.TradeVolume);              // 100 + 130
            Assert.Equal(5m, s.Rewards);
            Assert.Equal(2m, s.Fees);
            Assert.Equal(35m, s.NetTradingResultUsdc);      // -100 +130 +5 (exkl. Ein-/Auszahlungen)
            Assert.Equal(2, s.TradeCount);
            // Invariante: Endsaldo − Anfangssaldo = Handelsergebnis + Einzahlungen − Auszahlungen
            Assert.Equal(1185m, s.ClosingBalanceUsdc);      // 1000 + 35 + 200 - 50
            Assert.Equal(s.NetTradingResultUsdc + s.Deposits - s.Withdrawals, s.BalanceChange);
        }

        [Fact]
        public void Monthly_breakdown_chains_opening_balances()
        {
            var entries = new List<LedgerEntry>
            {
                E(LedgerEventType.Deposit, 100m, Jun, gross: 100m),   // Juni
                E(LedgerEventType.TradeSell, 40m, Jul, gross: 40m),   // Juli
            };
            var from = new DateTime(2026, 6, 1, 0, 0, 0, DateTimeKind.Utc);
            var to = new DateTime(2026, 7, 31, 23, 59, 59, DateTimeKind.Utc);

            var months = AccountingEngine.BuildMonthlyBreakdown(entries, from, to, 1);

            Assert.Equal(2, months.Count);
            Assert.Equal(0m, months[0].OpeningBalanceUsdc);     // Juni startet bei 0
            Assert.Equal(100m, months[0].ClosingBalanceUsdc);
            Assert.Equal(100m, months[1].OpeningBalanceUsdc);   // Juli erbt Juni-Endsaldo
            Assert.Equal(140m, months[1].ClosingBalanceUsdc);
        }

        // ---------------- FxConverter ----------------

        [Fact]
        public void Fx_conversions_usdc_usd_eur()
        {
            Assert.Equal(100m, FxConverter.UsdcToUsd(100m, 1.0m));
            Assert.Equal(92m, FxConverter.UsdToEur(100m, 0.92m));
            Assert.Equal(92m, FxConverter.UsdcToEur(100m, 0.92m, 1.0m));
        }

        [Fact]
        public void Fx_nearest_on_or_before_picks_latest_valid_rate()
        {
            var rates = new List<FxRate>
            {
                new() { Date = new DateTime(2026, 7, 3), UsdToEur = 0.90m },
                new() { Date = new DateTime(2026, 7, 5), UsdToEur = 0.92m },   // Freitag
            };
            // Sonntag 2026-07-05 gibt es keinen Kurs → letzter gültiger (Freitag).
            var pick = FxConverter.NearestOnOrBefore(rates, new DateTime(2026, 7, 6));
            Assert.NotNull(pick);
            Assert.Equal(0.92m, pick!.UsdToEur);

            Assert.Null(FxConverter.NearestOnOrBefore(rates, new DateTime(2026, 7, 1)));   // vor allen Kursen
        }

        // ---------------- CsvExporter ----------------

        [Fact]
        public void Csv_ledger_has_header_and_escapes_commas()
        {
            var entries = new List<LedgerEntry>
            {
                new() { AccountId = 1, EventType = LedgerEventType.TradeBuy, Timestamp = Jul,
                        MarketSlug = "will-x-win, really?", Side = "BUY", Size = 10m, PriceUsdc = 0.5m,
                        GrossUsdc = 5m, FeeUsdc = 0.1m, NetUsdc = -5.1m, TxHash = "0xabc", Source = "polymarket-activity" }
            };
            string csv = CsvExporter.Ledger(entries);
            var lines = csv.Split('\n', StringSplitOptions.RemoveEmptyEntries);

            Assert.StartsWith("Timestamp,AccountId,EventType", lines[0]);
            Assert.Contains("\"will-x-win, really?\"", lines[1]);   // Komma-Feld gequotet
            Assert.Contains("-5.1", lines[1]);                       // kulturinvariant (Punkt)
        }

        [Fact]
        public void Csv_statement_lists_key_figures()
        {
            var s = new PeriodStatement(1, Jul, Jul, 100m, 135m, 0m, 0m, 230m, 0m, 5m, 2m, 35m, 2, 3);
            string csv = CsvExporter.Statement(s);
            Assert.Contains("Endsaldo,135", csv);
            Assert.Contains("Netto-Handelsergebnis (Cash),35", csv);
        }

        // ---------------- PdfExporter (MigraDoc-Rendering headless) ----------------

        [Fact]
        public void Pdf_render_produces_valid_pdf_bytes()
        {
            var s = new PeriodStatement(1, Jul, Jul, 100m, 135m, 200m, 50m, 230m, 0m, 5m, 2m, 35m, 2, 1);
            var entries = new List<LedgerEntry>
            {
                new() { AccountId = 1, EventType = LedgerEventType.TradeSell, Timestamp = Jul,
                        MarketSlug = "market-x", Side = "SELL", Size = 10m, PriceUsdc = 0.5m,
                        GrossUsdc = 130m, FeeUsdc = 1m, NetUsdc = 129m, TxHash = "0xabc" }
            };

            byte[] pdf = PdfExporter.Render(s, new[] { s }, entries, "USDC", 1.0m, "Native Buchungswährung.");

            Assert.True(pdf.Length > 500);
            // PDF-Signatur %PDF-
            Assert.Equal(new byte[] { 0x25, 0x50, 0x44, 0x46 }, pdf.Take(4).ToArray());
        }
    }
}
