using Elwig.Dialogs;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Helpers.Printing;
using Elwig.Helpers.Weighing;
using Elwig.Models.Entities;
using Elwig.Windows;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace Elwig {
    public partial class App : Application {

        protected static App CurrentApp;
        public static int NumWindows => CurrentApp.Windows.Count;
        public static bool ForceShutdown { get; private set; } = false;

        private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
        public readonly SerialPortWatcher SerialPortWatcher = new();

        public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
        public static readonly string MailsPath = Path.Combine(DataPath, "mails");
        public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
        public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Elwig");
        public static readonly string DocumentsPath = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
            Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Elwig/Documents") :
            Path.Combine(InstallPath, "resources/Documents");
        public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");

        public static Config Config { get; private set; } = new(ConfigPath);
        public static Version Version { get; private set; } = new();

        public static int BranchNum { get; private set; }
        public static string ZwstId { get; private set; }
        public static string BranchName { get; private set; }
        public static int? BranchPlz { get; private set; }
        public static string? BranchLocation { get; private set; }
        public static string? BranchAddress { get; private set; }
        public static string? BranchPhoneNr { get; private set; }
        public static string? BranchFaxNr { get; private set; }
        public static string? BranchMobileNr { get; private set; }

        public static IList<IScale> Scales { get; private set; } = [];
        public static IList<ICommandScale> CommandScales => [.. Scales.Where(s => s is ICommandScale).Cast<ICommandScale>()];
        public static IList<IEventScale> EventScales => [.. Scales.Where(s => s is IEventScale).Cast<IEventScale>()];
        public static ClientParameters Client { get; set; }

        public static Dispatcher MainDispatcher { get; private set; }
        private DateTime LastChanged;
        private static DateTime CurrentLastWrite => File.GetLastWriteTime(Config.DatabaseFile);
        private readonly DispatcherTimer ContextTimer = new() { Interval = TimeSpan.FromSeconds(2) };

        public App() : base() {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            Directory.CreateDirectory(TempPath);
            Directory.CreateDirectory(DataPath);
            Directory.CreateDirectory(MailsPath);
            MainDispatcher = Dispatcher;
            Scales = [];
            CurrentApp = this;
            Utils.OverrideCulture();

            var args = Environment.GetCommandLineArgs();
            if (args.Length >= 2) {
                Config = new(Path.GetFullPath(args[1]));
            }

            ContextTimer.Tick += (object? sender, EventArgs evt) => {
                var ch = CurrentLastWrite;
                if (ch > LastChanged) {
                    LastChanged = ch;
                    OnContextChanged();
                }
            };
        }

        private static void OnContextChanged() {
            MainDispatcher.BeginInvoke(HintContextChange);
        }

        protected override async void OnStartup(StartupEventArgs evt) {
            Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0.0");

            try {
                await AppDbUpdater.CheckDb();
            } catch (Exception e) {
                if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) {
                    await CheckForUpdates();
                }
                MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error);
                Shutdown();
                return;
            }

            LastChanged = CurrentLastWrite;
            ContextTimer.Start();

            Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
            using (var ctx = new AppDbContext()) {
                branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
                try {
                    Client = new(ctx);
                } catch (Exception e) {
                    MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    Shutdown();
                    return;
                }
                BranchNum = branches.Count;
            }

            Utils.RunBackground("Temp File Cleanup", () => {
                Utils.CleanupTempFiles();
                return Task.CompletedTask;
            });

            Utils.RunBackground("HTML Initialization", () => Html.Init());
            Utils.RunBackground("PDF Initialization", () => Pdf.Init());
            Utils.RunBackground("JSON Schema Initialization", BillingData.Init);

            if (Config.UpdateAuto && Config.UpdateUrl != null) {
                if (Utils.HasInternetConnectivity()) {
                    Utils.RunBackground("Auto Updater", async () => {
                        await Task.Delay(1000);
                        await CheckForUpdates();
                    });
                }
                NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
                _autoUpdateTimer.Start();
            }

            SerialPortWatcher.SerialPortConnected += OnSerialPortConnected;
            SerialPortWatcher.SerialPortDisconnected += OnSerialPortDisconnected;

            var list = new List<IScale>();
            foreach (var s in Config.Scales) {
                try {
                    list.Add(Scale.FromConfig(s));
                } catch (Exception e) {
                    list.Add(new InvalidScale(s.Id));
                    if (s.Required)
                        MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
                            MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            Scales = list;

            if (Config.Branch != null) {
                if (!branches.ContainsKey(Config.Branch.ToLower())) {
                    MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
                    Shutdown();
                } else {
                    SetBranch(branches[Config.Branch.ToLower()]);
                }
            } else if (branches.Count == 1) {
                SetBranch(branches.First().Value);
            } else {
                MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
                Shutdown();
            }

            if (Config.WeighingMode == null) {
                if (Client.IsMatzen || Client.IsWolkersdorf) {
                    Config.WeighingMode = WeighingMode.Net;
                } else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
                    Config.WeighingMode = WeighingMode.Box;
                } else if (Client.IsBaden || Client.IsGrInzersdorf) {
                    Config.WeighingMode = WeighingMode.Gross;
                }
            }

            base.OnStartup(evt);

            var window = new MainWindow();
            window.Show();
        }

        private async void Application_Exit(object sender, ExitEventArgs evt) {
            SerialPortWatcher.Dispose();
            foreach (var s in EventScales) {
                s.Dispose();
            }
            await Pdf.Cleanup();
        }

        public static void SetBranch(Branch b) {
            SetBranch((b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
        }

        private static void SetBranch((string, string, int?, string?, string?, string?, string?, string?) entry) {
            ZwstId = entry.Item1;
            BranchName = entry.Item2;
            BranchPlz = entry.Item3;
            BranchLocation = entry.Item4?
                .Split(" in ")[0]
                .Split(" im ")[0]
                .Split(" an ")[0]
                .Split(" am ")[0]
                .Split(" bei ")[0]
                .Split(" beim ")[0];
            BranchAddress = entry.Item5;
            BranchPhoneNr = entry.Item6;
            BranchFaxNr = entry.Item7;
            BranchMobileNr = entry.Item8;
        }

        public static void HintContextChange() {
            if (CurrentApp == null) return;
            var ch = CurrentLastWrite;
            if (ch > CurrentApp.LastChanged)
                CurrentApp.LastChanged = ch;
            MainDispatcher.Invoke(() => {
                foreach (Window w in CurrentApp.Windows) {
                    if (w is not ContextWindow c) continue;
                    MainDispatcher.BeginInvoke(c.HintContextChange);
                }
            });
        }

        private void OnAutoUpdateTimer(object? sender, EventArgs? evt) {
            foreach (Window w in CurrentApp.Windows) {
                if (w is UpdateDialog) return;
            }
            if (Utils.HasInternetConnectivity()) {
                Utils.RunBackground("Auto Updater", async () => await CheckForUpdates());
            }
        }

        private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
            if (!evt.IsAvailable) return;
            if (Utils.HasInternetConnectivity()) {
                Utils.RunBackground("Auto Updater", async () => {
                    await Task.Delay(2000);
                    await CheckForUpdates();
                });
            }
        }

        private void OnSerialPortConnected(object? sender, string name) {
            for (var i = 0; i < Config.Scales.Count; i++) {
                var s = Config.Scales[i];
                if (s.Connection?.StartsWith($"serial://{name}:") ?? false) {
                    if (Scales[i] is InvalidScale) {
                        try {
                            Scales[i] = Scale.FromConfig(s);
                            MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
                        } catch (Exception e) {
                            Scales[i] = new InvalidScale(s.Id);
                            MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
                                MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                    } else if (Scales[i] is IEventScale) {
                        MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
            }
            UpdateScales();
        }

        private void OnSerialPortDisconnected(object? sender, string name) {
            for (var i = 0; i < Config.Scales.Count; i++) {
                var s = Config.Scales[i];
                if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) {
                    MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
                    if (Scales[i] is ICommandScale) {
                        try {
                            Scales[i].Dispose();
                        } catch {
                            // ignore
                        }
                        Scales[i] = new InvalidScale(s.Id);
                    }
                }
            }
            UpdateScales();
        }

        public static void UpdateScales() {
            foreach (Window w in CurrentApp.Windows) {
                if (w is DeliveryAdminWindow t && t.ViewModel.IsReceipt) {
                    t.UpdateScales();
                }
            }
        }

        public static async Task CheckForUpdates(bool showAlert = false) {
            if (Config.UpdateUrl == null) return;
            var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
            if (latest != null && new Version(latest.Value.Version) > Version) {
                await MainDispatcher.BeginInvoke(() => {
                    var d = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size);
                    if (d.ShowDialog() == true) {
                        ForceShutdown = true;
                        Current.Shutdown();
                    }
                });
            } else if (showAlert) {
                if (latest == null) {
                    MessageBox.Show("Informationen konnten nicht abgerufen werden!", "Nach Updates suchen",
                        MessageBoxButton.OK, MessageBoxImage.Error);
                } else {
                    MessageBox.Show($"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})", "Nach Updates suchen",
                        MessageBoxButton.OK, MessageBoxImage.Information);
                }
            }
        }

        public static async Task ReplaceDatabase(string filename) {
            try {
                await Task.Run(async () => {
                    await Database.Import(filename);
                });
                MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
                ForceShutdown = true;
                Current.Shutdown();
            } catch (Exception exc) {
                MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window {
            foreach (Window w in CurrentApp.Windows) {
                if (w is T t && (selector == null || selector(t))) {
                    if (t.WindowState == WindowState.Minimized)
                        t.WindowState = WindowState.Normal;
                    t.Activate();
                    return t;
                }
            }
            var n = constructor();
            n.Show();
            return n;
        }

        public static DeliveryAdminWindow FocusReceipt() {
            return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.ViewModel.IsReceipt);
        }

        public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) {
            return FocusWindow<DeliveryAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr);
        }

        public static AreaComAdminWindow FocusMemberAreaComs(int mgnr) {
            return FocusWindow<AreaComAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr);
        }

        public static BaseDataWindow FocusBaseData() {
            return FocusWindow<BaseDataWindow>(() => new());
        }

        public static BaseDataWindow FocusBaseDataAreaComType() {
            var w = FocusBaseData();
            w.AreaCommitmentTypes.Focus();
            return w;
        }

        public static BaseDataWindow FocusBaseDataSeason(int year) {
            var w = FocusBaseData();
            w.Seasons.Focus();
            ControlUtils.SelectItemWithPk(w.SeasonList, year);
            return w;
        }

        public static OriginHierarchyWindow FocusOriginHierarchy() {
            return FocusWindow<OriginHierarchyWindow>(() => new());
        }

        public static OriginHierarchyWindow FocusOriginHierarchyKg(int kgnr) {
            var w = FocusOriginHierarchy();
            w.FocusKgNr(kgnr);
            return w;
        }

        public static DeliveryAncmtAdminWindow FocusDeliveryAncmt() {
            return FocusWindow<DeliveryAncmtAdminWindow>(() => new());
        }

        public static DeliveryScheduleAdminWindow FocusDeliverySchedule() {
            return FocusWindow<DeliveryScheduleAdminWindow>(() => new());
        }

        public static PaymentVariantsWindow FocusPaymentVariants(int year) {
            return FocusWindow<PaymentVariantsWindow>(() => new(year), w => w.Year == year);
        }

        public static PaymentAdjustmentWindow FocusPaymentAdjustment(int year) {
            return FocusWindow<PaymentAdjustmentWindow>(() => new(year), w => w.Year == year);
        }

        public static ChartWindow FocusChartWindow(int year, int avnr) {
            return FocusWindow<ChartWindow>(() => new(year, avnr), w => w.Year == year && w.AvNr == avnr);
        }

        public static MemberAdminWindow FocusMember(int mgnr) {
            var w = FocusWindow<MemberAdminWindow>(() => new());
            w.FocusMember(mgnr);
            return w;
        }

        public static MailWindow FocusMailWindow(int? year = null) {
            return FocusWindow<MailWindow>(() => new(year), w => year == null || w.Year == year);
        }
    }
}
