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

namespace PolyTrader.Modules.Accounting.Persistence
{
    /// <summary>Append-only Ledger-Zugriff mit idempotentem Upsert (Doppel-Buchen ausgeschlossen).</summary>
    public interface ILedgerRepository
    {
        /// <summary>Fügt den Satz ein, falls sein IdempotencyKey neu ist. true = neu gebucht, false = Duplikat.</summary>
        bool Upsert(LedgerEntry entry);
        DateTime? LatestTimestamp(int accountId);
        decimal SumNet(int accountId);
        int Count(int accountId);
        List<LedgerEntry> Query(int? accountId, DateTime? from, DateTime? to, int limit);
        /// <summary>ALLE Sätze des Scopes bis <paramref name="to"/> (für die Abrechnung inkl. Anfangssaldo).</summary>
        List<LedgerEntry> GetUpTo(int? accountId, DateTime to);
    }

    public interface IIngestRunRepository
    {
        void Insert(IngestRun run);   // setzt Id
        void Update(IngestRun run);
        List<IngestRun> GetRecent(int? accountId, int limit);
    }

    public interface IRawSnapshotRepository
    {
        void Insert(RawSnapshot snapshot);
    }

    /// <summary>Amtliche FX-Tageskurse (USD→EUR), versioniert. Upsert je Datum.</summary>
    public interface IFxRateRepository
    {
        void Upsert(FxRate rate);
        List<FxRate> GetAll();
    }

    // ---------------- EF-Implementierungen ----------------

    public class EfLedgerRepository : ILedgerRepository
    {
        private readonly IDbContextFactory<AccountingDbContext> _factory;
        public EfLedgerRepository(IDbContextFactory<AccountingDbContext> factory) => _factory = factory;

        public bool Upsert(LedgerEntry entry)
        {
            using var ctx = _factory.CreateDbContext();
            bool exists = ctx.Ledger.AsNoTracking().Any(x => x.IdempotencyKey == entry.IdempotencyKey);
            if (exists) return false;
            ctx.Ledger.Add(entry);
            ctx.SaveChanges();
            return true;
        }

        public DateTime? LatestTimestamp(int accountId)
        {
            using var ctx = _factory.CreateDbContext();
            return ctx.Ledger.AsNoTracking()
                .Where(x => x.AccountId == accountId)
                .OrderByDescending(x => x.Timestamp)
                .Select(x => (DateTime?)x.Timestamp)
                .FirstOrDefault();
        }

        public decimal SumNet(int accountId)
        {
            using var ctx = _factory.CreateDbContext();
            return ctx.Ledger.AsNoTracking().Where(x => x.AccountId == accountId).Sum(x => (decimal?)x.NetUsdc) ?? 0m;
        }

        public int Count(int accountId)
        {
            using var ctx = _factory.CreateDbContext();
            return ctx.Ledger.AsNoTracking().Count(x => x.AccountId == accountId);
        }

        public List<LedgerEntry> Query(int? accountId, DateTime? from, DateTime? to, int limit)
        {
            using var ctx = _factory.CreateDbContext();
            var q = ctx.Ledger.AsNoTracking().AsQueryable();
            if (accountId.HasValue) q = q.Where(x => x.AccountId == accountId.Value);
            if (from.HasValue) q = q.Where(x => x.Timestamp >= from.Value);
            if (to.HasValue) q = q.Where(x => x.Timestamp <= to.Value);
            return q.OrderByDescending(x => x.Timestamp).Take(limit).ToList();
        }

        public List<LedgerEntry> GetUpTo(int? accountId, DateTime to)
        {
            using var ctx = _factory.CreateDbContext();
            var q = ctx.Ledger.AsNoTracking().Where(x => x.Timestamp <= to);
            if (accountId.HasValue) q = q.Where(x => x.AccountId == accountId.Value);
            return q.OrderBy(x => x.Timestamp).ToList();
        }
    }

    public class EfIngestRunRepository : IIngestRunRepository
    {
        private readonly IDbContextFactory<AccountingDbContext> _factory;
        public EfIngestRunRepository(IDbContextFactory<AccountingDbContext> factory) => _factory = factory;

        public void Insert(IngestRun run)
        {
            using var ctx = _factory.CreateDbContext();
            ctx.IngestRuns.Add(run);
            ctx.SaveChanges();   // füllt run.Id (Autoincrement)
        }

        public void Update(IngestRun run)
        {
            using var ctx = _factory.CreateDbContext();
            ctx.IngestRuns.Update(run);
            ctx.SaveChanges();
        }

        public List<IngestRun> GetRecent(int? accountId, int limit)
        {
            using var ctx = _factory.CreateDbContext();
            var q = ctx.IngestRuns.AsNoTracking().AsQueryable();
            if (accountId.HasValue) q = q.Where(x => x.AccountId == accountId.Value);
            return q.OrderByDescending(x => x.StartedAt).Take(limit).ToList();
        }
    }

    public class EfRawSnapshotRepository : IRawSnapshotRepository
    {
        private readonly IDbContextFactory<AccountingDbContext> _factory;
        public EfRawSnapshotRepository(IDbContextFactory<AccountingDbContext> factory) => _factory = factory;

        public void Insert(RawSnapshot snapshot)
        {
            using var ctx = _factory.CreateDbContext();
            ctx.RawSnapshots.Add(snapshot);
            ctx.SaveChanges();
        }
    }

    public class EfFxRateRepository : IFxRateRepository
    {
        private readonly IDbContextFactory<AccountingDbContext> _factory;
        public EfFxRateRepository(IDbContextFactory<AccountingDbContext> factory) => _factory = factory;

        public void Upsert(FxRate rate)
        {
            using var ctx = _factory.CreateDbContext();
            var existing = ctx.FxRates.Find(rate.Date.Date);
            if (existing == null) ctx.FxRates.Add(new FxRate { Date = rate.Date.Date, UsdToEur = rate.UsdToEur, Source = rate.Source });
            else { existing.UsdToEur = rate.UsdToEur; existing.Source = rate.Source; }
            ctx.SaveChanges();
        }

        public List<FxRate> GetAll()
        {
            using var ctx = _factory.CreateDbContext();
            return ctx.FxRates.AsNoTracking().OrderBy(x => x.Date).ToList();
        }
    }
}
