using Microsoft.EntityFrameworkCore;
using Elwig.Models;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System;
using System.Windows;
using Microsoft.Extensions.Logging;

namespace Elwig.Helpers {
    public class AppDbContext : DbContext {

        public DbSet<Country> Countries { get; private set; }
        public DbSet<Currency> Currencies { get; private set; }
        public DbSet<AT_Gem> Gemeinden { get; private set; }
        public DbSet<AT_Kg> Katastralgemeinden { get; private set; }
        public DbSet<AT_Ort> Orte { get; private set; }
        public DbSet<AT_Plz> Postleitzahlen { get; private set; }
        public DbSet<AT_PlzDest> PlzDestinations { get; private set; }
        public DbSet<PostalDest> PostalDestinations { get; private set; }
        public DbSet<WineOrigin> WineOrigins { get; private set; }
        public DbSet<WineQualLevel> WineQualityLevels { get; private set; }
        public DbSet<WineVar> WineVarieties { get; private set; }

        public DbSet<ClientParam> ClientParameters { get; private set; }
        public DbSet<WbKg> WbKgs { get; private set; }
        public DbSet<WbRd> WbRde { get; private set; }
        public DbSet<WineAttr> WineAttributes { get; private set; }
        public DbSet<WineCult> WineCultivations { get; private set; }
        public DbSet<Branch> Branches { get; private set; }
        public DbSet<Member> Members { get; private set; }
        public DbSet<BillingAddr> BillingAddresses { get; private set; }
        public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
        public DbSet<AreaCom> AreaCommitments { get; private set; }
        public DbSet<AreaComAttr> AreaCommitmentAttributes { get; private set; }
        public DbSet<Season> Seasons { get; private set; }
        public DbSet<Modifier> Modifiers { get; private set; }
        public DbSet<Delivery> Deliveries { get; private set; }
        public DbSet<DeliveryPart> DeliveryParts { get; private set; }
        public DbSet<DeliveryPartAttr> DeliveryPartAttributes { get; private set; }
        public DbSet<DeliveryPartModifier> DeliveryPartModifiers { get; private set; }

        private readonly StreamWriter? LogFile = null;
        public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile);
        public DateTime SavedLastWriteTime { get; private set; }
        public bool HasBackendChanged => SavedLastWriteTime != LastWriteTime;

        public AppDbContext() {
            if (App.Config.DatabaseLog != null) {
                try {
                    var file = File.Open(App.Config.DatabaseLog, FileMode.Append, FileAccess.Write, FileShare.Write);
                    LogFile = new(file) {
                        AutoFlush = true
                    };
                } catch (Exception e) {
                    MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            SavedLastWriteTime = LastWriteTime;
            SavedChanges += OnSavedChanges;

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
            optionsBuilder.UseSqlite($"Data Source=\"{App.Config.DatabaseFile}\"; Foreign Keys=True; Mode=ReadWrite; Cache=Default");
            optionsBuilder.UseLazyLoadingProxies();
            optionsBuilder.LogTo(Log, LogLevel.Information);
            base.OnConfiguring(optionsBuilder);
        }

        public override void Dispose() {
            base.Dispose();
            LogFile?.Dispose();
            GC.SuppressFinalize(this);
        }

        private void OnSavedChanges(object? sender, SavedChangesEventArgs evt) {
            SavedLastWriteTime = LastWriteTime;
        }

        protected void Log(string msg) {
            LogFile?.WriteLine(msg);
        }

        public async Task<bool> MgNrExists(int mgnr) {
            return await Members.FindAsync(mgnr) != null;
        }

        public async Task<bool> FbNrExists(int fbnr) {
            return await AreaCommitments.FindAsync(fbnr) != null;
        }

        public async Task<bool> SortIdExists(string sortId) {
            return await WineVarieties.FindAsync(sortId) != null;
        }

        public async Task<bool> AttrIdExists(string attrId) {
            return await WineAttributes.FindAsync(attrId) != null;
        }

        public async Task<int> NextMgNr() {
            int c = await Members.Select(m => m.MgNr).MinAsync();
            (await Members.OrderBy(m => m.MgNr).Select(m => m.MgNr).ToListAsync())
                .ForEach(a => { if (a <= c + 100) c = a; });
            return c + 1;
        }

        public async Task<int> NextFbNr() {
            int c = await AreaCommitments.Select(ac => ac.FbNr).MinAsync();
            (await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
                .ForEach(a => { if (a <= c + 100) c = a; });
            return c + 1;
        }

        public async Task<int> NextLNr(DateOnly date) {
            var dateStr = date.ToString("yyyy-MM-dd");
            int c = 0;
            (await Deliveries.Where(d => d.DateString == dateStr).Select(d => d.LNr).ToListAsync())
                .ForEach(a => { if (a <= c + 10) c = a; });
            return c + 1;
        }
    }
}