/*
<copyright file="BGEditorEMJobs.cs" company="BansheeGz">
    Copyright (c) 2019-2024 All Rights Reserved
</copyright>
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace BansheeGz.BGDatabase.Editor
{
    public class BGEditorEMJobs : BGDatabaseExcelImportEditorTool.Worker
    {
        private const string Key_ExcelOn = "BGD.EEM.On";
        private const string Key_Jobs = "BGD.EEM.Jobs";
        private const string Key_ExcelSaveDatabase = "BGD.EEM.Autosave";

        private readonly BGEditorEMExecutor executor;
        private readonly List<RunningWatcher> watchers = new List<RunningWatcher>();
        private readonly BGTabbedViewCompact tabbedView;
        private BGLogger lastLog;
        private bool excelOn;

        public bool ExcelOn
        {
            set
            {
                BGEditorEMParameters.SetBool(Key_ExcelOn, ref excelOn, value);

                if (value) StartWatching();
                else StopWatching();
            }
        }

        private bool excelSaveDatabase;

        public bool ExcelSaveDatabase
        {
            set => BGEditorEMParameters.SetBool(Key_ExcelSaveDatabase, ref excelSaveDatabase, value);
        }

        private static List<string> Jobs
        {
            get
            {
                var result = new List<string>();
                var jobsAsString = BGEditorEMParameters.GetString(Key_Jobs);
                if (!string.IsNullOrEmpty(jobsAsString)) result.AddRange(jobsAsString.Split(new char[]{','}));
                return result;
            }
        }

        public BGEditorEMJobs(BGEditorEMExecutor executor)
        {
            this.executor = executor;
            executor.RunOnMainThread(() =>
            {
                excelOn = BGEditorEMParameters.GetBool(Key_ExcelOn);
                excelSaveDatabase = BGEditorEMParameters.GetBool(Key_ExcelSaveDatabase);
                if (excelOn) StartWatching();
            });

            tabbedView = new BGTabbedViewCompact(
                new string[] { "Parameters", "Last log" },
                new BGView[]
                {
                    new BGView.DefaultView(ShowParams),
                    new BGScrollView.DefaultScrollView(() =>
                    {
                        //logs
                        BGEditorUtility.Log(lastLog==null?"[no log]":lastLog.Log);
                    }, false, false)
                }
            ){ExpandHeight = true};
        }

        private void StartWatching()
        {
            StopWatching();

            if (!BGRepo.DefaultRepoLoaded) BGRepo.Load();
            if (!BGSettingsEditor.OkWithLoad)
            {
                Error($"BGDatabase settings can not be loaded, error={BGSettingsEditor.ErrorOnLoad}");
                return;
            }

            var jobs = Jobs;
            foreach (var job in jobs)
            {
                var jobModel = BGSettingsEditor.Model.SyncList.Find(model => model.Id.ToString() == job);
                if (Error(jobModel == null, $"Excel job with id={job} is not found")) continue;
                if (Error(jobModel.UpdateIdsOnImport, $"Excel job={jobModel.Name} has UpdateIdsOnImport parameter on, can not run a job with UpdateIdsOnImport=true")) continue;
                var dsModel = BGSettingsEditor.Model.DataSourceList.Find(model => model.DataSourceId == jobModel.DataSourceId);
                if (Error(dsModel == null, $"Excel job={jobModel.Name} does not reference a data source")) continue;
                var dsExcel = dsModel.DataSource as BGDsExcel;
                if (Error(dsExcel == null, $"Excel job={jobModel.Name} does not reference Excel data source")) continue;
                var filePath = dsExcel.RealPath;
                if (Error(string.IsNullOrEmpty(filePath), $"File is not set for DataSource={dsModel.Name}")) continue;
                if (Error(!File.Exists(filePath), $"File={filePath} for DataSource={dsModel.Name} does not exists")) continue;

                watchers.Add(new RunningWatcher(jobModel, filePath, () =>
                {
                    executor.RunOnMainThread(() =>
                    {
                        if (Error(jobModel.UpdateIdsOnImport, $"Excel job={jobModel.Name} has UpdateIdsOnImport parameter on, can not run a job with UpdateIdsOnImport=true")) return;
                        var excelJob = new BGSyncExcel();
                        excelJob.Init(jobModel);
                        lastLog = new BGLogger();
                        excelJob.RunImport(lastLog);
                        if (excelSaveDatabase)
                        {
                            BGRepoSaver.SaveRepo();
                            BGRepo.I.Events.FireAnyChange();
                            if (BGRepoWindow.Active) BGRepoWindow.Instance.MarkRepoSaved();
                        }
                    });
                }));
            }
        }

        private static bool Error(bool condition, string message)
        {
            if (!condition) return false;
            Error(message);
            return true;
        }

        private static void Error(string message) => Debug.Log("BGDatabase File Monitor error: " + message);

        private void StopWatching()
        {
            foreach (var watcher in watchers) watcher.Dispose();
            watchers.Clear();
        }

        public void Dispose() => StopWatching();

        public void Gui() => tabbedView.Gui();

        private void ShowParams()
        {
            BGEditorUtility.Horizontal(() =>
            {
                BGEditorUtility.Toggle(new GUIContent("On", "Is background monitor is on"), excelOn, v => ExcelOn = v);
                using (BGUsingEditor.DisableGUI(!excelOn))
                {
                    if (BGEditorUtility.Button("Restart", 100)) StartWatching();
                }
                using (BGUsingEditor.DisableGUI(lastLog==null))
                {
                    if (BGEditorUtility.Button("Clear log", 100)) lastLog = null;
                }

                GUILayout.FlexibleSpace();
            });
            if (!excelOn) GUILayout.Label("Monitor is off", BGStyle.Attention);

            BGEditorUtility.Toggle(new GUIContent("Auto-Save database", "If this setting is on, all changes to BGDatabase from Excel file will be auto-saved"),
                excelSaveDatabase, v => ExcelSaveDatabase = v);

            BGEditorUtility.TabPanel("Jobs", () =>
            {
                var jobs = Jobs;
                var targetJobType = typeof(BGSyncExcel).FullName;
                for (var i = 0; i < jobs.Count; i++)
                {
                    var job = jobs[i];
                    BGEditorUtility.Horizontal(() =>
                    {
                        var model = BGSettingsEditor.Model.SyncList.Find(m => m.Id.ToString() == job);
                        string label;
                        if (model == null) label = job + "[not found!]";
                        else label = model.Name;
                        BGEditorUtility.Label(i + ") " + label);
                        if (GUILayout.Button("Delete", BGStyle.Button))
                        {
                            var jobsString = "";
                            foreach (var job2 in jobs)
                            {
                                if (job2 == job) continue;
                                if (jobsString.Length > 0) jobsString += ',';
                                jobsString += job2;
                            }

                            BGEditorEMParameters.SetString(Key_Jobs, jobsString);
                            if (excelOn) StartWatching();
                            GUIUtility.ExitGUI();
                        }

                        GUILayout.FlexibleSpace();
                    });
                }

                if (BGEditorUtility.Button("Add a job", 100))
                {
                    var addable = BGSettingsEditor.Model.SyncList.Where(
                        model => !jobs.Contains(model.Id.ToString()) && model.JobType == targetJobType).ToArray();
                    if (addable.Length == 0) EditorUtility.DisplayDialog("Information", "There is no job available for adding", "Ok");
                    else
                    {
                        var menu = new GenericMenu();
                        foreach (var model in addable)
                        {
                            menu.AddItem(new GUIContent(model.Name), false, () =>
                            {
                                if (model.UpdateIdsOnImport)
                                {
                                    EditorUtility.DisplayDialog("Information", "Can not add a job with 'UpdateIdsOnImport' parameter on", "Ok");
                                    return;
                                }
                                var jobsAsString = BGEditorEMParameters.GetString(Key_Jobs) ?? "";
                                if (jobsAsString.Length > 0) jobsAsString += ',';
                                jobsAsString += model.Id;
                                BGEditorEMParameters.SetString(Key_Jobs, jobsAsString);
                                if (excelOn) StartWatching();
                            });
                        }

                        menu.ShowAsContext();
                    }
                }
            });
            BGEditorUtility.TabPanel("Monitoring files", () =>
            {
                if (watchers.Count == 0) BGEditorUtility.Label("None");
                else
                {
                    for (var i = 0; i < watchers.Count; i++) BGEditorUtility.Label(i + ") " + watchers[i].FilePath);
                }
            });
        }

        private class RunningWatcher : IDisposable
        {
            private readonly BGSyncModel model;
            private readonly string filePath;
            private readonly Action action;
            private BGEditorEMFileWatcher watcher;

            public BGSyncModel Model => model;

            public string FilePath => filePath;

            public RunningWatcher(BGSyncModel model, string filePath, Action action)
            {
                this.model = model;
                this.filePath = filePath;
                this.action = action;
                watcher = new BGEditorEMFileWatcher(filePath, action);
            }

            // private void OnFileChange(object sender, FileSystemEventArgs e) => action.Invoke();

            public void Dispose()
            {
                watcher?.Dispose();
                watcher = null;
            }
        }
    }
}