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.Globalization;
using System.Threading;
using System.Windows.Markup;
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;

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 ExePath = @"C:\Program Files\Elwig\";
        public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
        public static readonly Config Config = new(DataPath + "config.ini");

        public static int VersionMajor { get; private set; }
        public static int VersionMinor { get; private set; }
        public static int VersionPatch { get; private set; }
        public static string Version {
            get => $"{VersionMajor}.{VersionMinor}.{VersionPatch}";
            private set {
                var p = value.Split(".").Select(p => int.Parse(p.Trim())).ToArray();
                VersionMajor = p.ElementAtOrDefault(0);
                VersionMinor = p.ElementAtOrDefault(1);
                VersionPatch = p.ElementAtOrDefault(2);
            }
        }

        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() {
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
            Directory.CreateDirectory(TempPath);
            Directory.CreateDirectory(DataPath);
            MainDispatcher = Dispatcher;
            Scales = [];
            CurrentApp = this;
            OverrideCulture();

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

        private static void OnContextChanged() {
            MainDispatcher.BeginInvoke(async () => await HintContextChange());
        }

        private static void OverrideCulture() {
            var locale = new CultureInfo("de-AT", false);
            locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
            locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
            locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
            CultureInfo.CurrentCulture = locale;
            CultureInfo.CurrentUICulture = locale;
            Thread.CurrentThread.CurrentCulture = locale;
            Thread.CurrentThread.CurrentUICulture = locale;
            CultureInfo.DefaultThreadCurrentCulture = locale;
            CultureInfo.DefaultThreadCurrentUICulture = locale;
            FrameworkElement.LanguageProperty.OverrideMetadata(
                typeof(FrameworkElement),
                new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
            );
        }

        protected override async void OnStartup(StartupEventArgs evt) {
            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));
                    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);
        }

        private async void Application_Exit(object sender, ExitEventArgs evt) {
            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(" im ")[0].Split(" an ")[0].Split(" bei ")[0];  // FIXME
            BranchAddress = entry.Item5;
            BranchPhoneNr = entry.Item6;
            BranchFaxNr = entry.Item7;
            BranchMobileNr = entry.Item8;
        }

        public static async Task HintContextChange() {
            CurrentApp.LastChanged = CurrentLastWrite;
            foreach (Window w in CurrentApp.Windows) {
                if (w is not ContextWindow c) continue;
                await 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", CheckForUpdates);
            }
        }

        public static async Task CheckForUpdates() {
            if (Config.UpdateUrl == null) return;
            var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
            if (latest != null && new Version(latest.Value.Version) > new 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();
                    }
                });
            }
        }

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

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

        public static AreaComAdminWindow FocusMemberAreaComs(int mgnr) {
            return FocusWindow<AreaComAdminWindow>(() => new(mgnr), w => w.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 SeasonFinishWindow FocusSeasonFinish() {
            return FocusWindow<SeasonFinishWindow>(() => new());
        }

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

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

        public static PaymentVariantsWindow FocusPaymentVariants(int year) {
            return FocusWindow<PaymentVariantsWindow>(() => 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);
        }
    }
}
