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

namespace PolyTrader.Modules.Accounting.Logic
{
    /// <summary>
    /// Neutrale Periodenabrechnung (A-2): aggregiert die Ledger-Sätze eines Zeitraums (× Account oder
    /// alle) zu einer prüfbaren Aufstellung — länderneutral, ohne steuerliche Einordnung (die kommt
    /// getrennt in der US-Steuerschicht A-3). Alle Beträge in USDC (native Buchungswährung); die
    /// FX-Umrechnung nach USD/EUR liegt separat im <see cref="FxConverter"/>.
    /// </summary>
    public sealed record PeriodStatement(
        int? AccountId,
        DateTime From,
        DateTime To,
        decimal OpeningBalanceUsdc,
        decimal ClosingBalanceUsdc,
        decimal Deposits,
        decimal Withdrawals,
        decimal TradeVolume,
        decimal Redeems,
        decimal Rewards,
        decimal Fees,
        decimal NetTradingResultUsdc,   // operatives Ergebnis (Cash-Basis, EXKL. Ein-/Auszahlungen)
        int TradeCount,
        int EntryCount)
    {
        /// <summary>Invariante: Endsaldo − Anfangssaldo = Handelsergebnis + Einzahlungen − Auszahlungen.</summary>
        public decimal BalanceChange => ClosingBalanceUsdc - OpeningBalanceUsdc;
    }

    public static class AccountingEngine
    {
        private static bool IsCashflowType(LedgerEventType t) =>
            t is LedgerEventType.Deposit or LedgerEventType.Withdrawal;

        /// <summary>
        /// Baut die Abrechnung für [<paramref name="from"/>, <paramref name="to"/>]. <paramref name="allUpToTo"/>
        /// enthält ALLE Ledger-Sätze des Scopes bis <paramref name="to"/> (für den Anfangssaldo werden die
        /// Sätze vor <paramref name="from"/> kumuliert). Grenzen inklusive.
        /// </summary>
        public static PeriodStatement BuildStatement(
            IEnumerable<LedgerEntry> allUpToTo, DateTime from, DateTime to, int? accountId)
        {
            var list = allUpToTo.Where(e => e.Timestamp <= to).ToList();

            decimal opening = list.Where(e => e.Timestamp < from).Sum(e => e.NetUsdc);
            var period = list.Where(e => e.Timestamp >= from && e.Timestamp <= to).ToList();

            decimal Sum(Func<LedgerEntry, bool> pred, Func<LedgerEntry, decimal> sel) =>
                period.Where(pred).Sum(sel);

            decimal deposits = Sum(e => e.EventType == LedgerEventType.Deposit, e => e.GrossUsdc);
            decimal withdrawals = Sum(e => e.EventType == LedgerEventType.Withdrawal, e => e.GrossUsdc);
            decimal tradeVolume = Sum(e => e.EventType is LedgerEventType.TradeBuy or LedgerEventType.TradeSell, e => e.GrossUsdc);
            decimal redeems = Sum(e => e.EventType == LedgerEventType.Redeem, e => e.GrossUsdc);
            decimal rewards = Sum(e => e.EventType == LedgerEventType.Reward, e => e.GrossUsdc);
            decimal fees = period.Sum(e => e.FeeUsdc);
            decimal netTrading = period.Where(e => !IsCashflowType(e.EventType)).Sum(e => e.NetUsdc);
            decimal closing = opening + period.Sum(e => e.NetUsdc);
            int tradeCount = period.Count(e => e.EventType is LedgerEventType.TradeBuy or LedgerEventType.TradeSell);

            return new PeriodStatement(accountId, from, to, opening, closing, deposits, withdrawals,
                tradeVolume, redeems, rewards, fees, netTrading, tradeCount, period.Count);
        }

        /// <summary>
        /// Zerlegt den Zeitraum in Kalendermonate und liefert je Monat eine Abrechnung (für den
        /// BWA-Perioden-/Monatsvergleich). Anfangssaldo jedes Monats = Endsaldo des Vormonats.
        /// </summary>
        public static List<PeriodStatement> BuildMonthlyBreakdown(
            IEnumerable<LedgerEntry> allUpToTo, DateTime from, DateTime to, int? accountId)
        {
            var list = allUpToTo.ToList();
            var result = new List<PeriodStatement>();
            var monthStart = new DateTime(from.Year, from.Month, 1, 0, 0, 0, DateTimeKind.Utc);

            while (monthStart <= to)
            {
                DateTime monthEnd = monthStart.AddMonths(1).AddTicks(-1);
                DateTime windowFrom = monthStart < from ? from : monthStart;
                DateTime windowTo = monthEnd > to ? to : monthEnd;
                result.Add(BuildStatement(list, windowFrom, windowTo, accountId));
                monthStart = monthStart.AddMonths(1);
            }
            return result;
        }
    }
}
