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

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 static readonly string DataPath = @"C:\ProgramData\Elwig\";
        public static readonly string MailsPath = Path.Combine(DataPath, "mails");
        public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
        public static readonly string ExePath = @"C:\Program Files\Elwig\";
        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>().ToList();
        public static IList<IEventScale> EventScales => Scales.Where(s => s is IEventScale).Cast<IEventScale>().ToList();
        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");

            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(500);
                        await CheckForUpdates();
                    });
                }
                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
                _autoUpdateTimer.Start();
            }

            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($"Unable to create scale {s.Id}:\n\n{e.Message}", "Scale Error",
                            MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            Scales = list;

            if (Config.Branch != null) {
                if (!branches.ContainsKey(Config.Branch.ToLower())) {
                    MessageBox.Show("Invalid branch name in config!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
                    Shutdown();
                } else {
                    SetBranch(branches[Config.Branch.ToLower()]);
                }
            } else if (branches.Count == 1) {
                SetBranch(branches.First().Value);
            } else {
                MessageBox.Show("Unable to determine local branch!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
                Shutdown();
            }

            base.OnStartup(evt);

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

        private async void Application_Exit(object sender, ExitEventArgs evt) {
            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;
            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());
            }
        }

        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);
                }
            }
        }

        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);
        }
    }
}