using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Forms.Design;
using System.Windows.Media.Imaging;
using InABox.Core;
using InABox.Scripting;
using InABox.WPF;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Win32;

namespace InABox.DynamicGrid
{
    public delegate bool OnImportItem(object o);

    public class CustomiseImportArgs
    {
        public string FileName { get; set; }
    }

    public delegate void OnCustomiseImport(object sender, CustomiseImportArgs e);

    public class DynamicImportGrid : DynamicDataGrid<Importer>
    {
        private static readonly BitmapImage run = Wpf.Resources.rightarrow.AsBitmapImage();

        private DynamicImageColumn ImportColumn;

        private bool _canImport = true;
        public bool CanImport
        {
            get => _canImport;
            set
            {
                if(_canImport != value)
                {
                    _canImport = value;
                    ImportColumn.Position = _canImport ? DynamicActionColumnPosition.End : DynamicActionColumnPosition.Hidden;
                    Refresh(true, false);
                }
            }
        }

        protected override void Init()
        {
            base.Init();
            ImportColumn = new DynamicImageColumn(ImportImage, ImportAction);
            ActionColumns.Add(ImportColumn);

            HiddenColumns.Add(x => x.EntityName);
            HiddenColumns.Add(x => x.ImporterDescription);
            HiddenColumns.Add(x => x.FileName);
            HiddenColumns.Add(x => x.Definition);
            HiddenColumns.Add(x => x.HasHeader);
            HiddenColumns.Add(x => x.HeaderRows);
            HiddenColumns.Add(x => x.ColumnWidths);
            HiddenColumns.Add(x => x.Script);
            HiddenColumns.Add(x => x.Settings);
        }

        protected override void DoReconfigure(DynamicGridOptions options)
        {
            base.DoReconfigure(options);

            options.RecordCount = true;
            options.ShowHelp = true;
        }

        public Type EntityType { get; set; }

        public Guid EntityID { get; set; }

        public event OnImportItem OnImportItem;

        public event OnCustomiseImport OnCustomiseImport;

        public event ImportSaveEvent OnSave;

        public event ImportLoadEvent OnLoad;

        protected override void ShowHelp(string slug)
        {
            base.ShowHelp("Import_Data");
        }

        private BitmapImage? ImportImage(CoreRow? arg)
        {
            return arg != null ? run : null;
        }

        public static bool CreateImporter(Importer importer,
            [NotNullWhen(true)]
            ref string? filename,
            [NotNullWhen(true)]
            out IImporter? iimporter,
            Action<CustomiseImportArgs>? customiseImport = null,
            Func<object, bool>? onImport = null)
        {
            iimporter = null;
            var definition =
                ImportFactory.Definitions.FirstOrDefault(x => x.Description.Equals(importer.ImporterDescription));
            var entityType = CoreUtils.GetEntity(importer.EntityName);
            if (definition != null)
            {
                ScriptDocument? helper = null;
                try
                {
                    if (!importer.Script.IsNullOrWhiteSpace())
                    {
                        helper = new ScriptDocument(importer.Script);
                        if (!helper.Compile())
                        {
                            MessageBox.Show("Unable to Compile Import Helper Script!");
                            return false;
                        }
                    }
                }
                catch (Exception e)
                {
                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                }

                if(filename is null)
                {
                    var fullFileName = importer.FileName;
                    var path = Path.GetDirectoryName(fullFileName);
                    var extension = Path.GetExtension(fullFileName);
                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(fullFileName) };
                    customiseImport?.Invoke(args);

                    var filename2 = CoreUtils.SanitiseFileName(args.FileName);
                    var dlg = new OpenFileDialog
                    {
                        Filter = definition.Filter,
                        FileName = Path.Combine(path, filename2) + (extension.StartsWith(".") ? "" : ".") + extension
                    };
                    if (!string.IsNullOrWhiteSpace(dlg.FileName) && Directory.Exists(Path.GetDirectoryName(dlg.FileName)))
                        dlg.InitialDirectory = Path.GetDirectoryName(dlg.FileName);
                    if(dlg.ShowDialog() == true)
                    {
                        filename = dlg.FileName;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    var path = Path.GetDirectoryName(filename);
                    var extension = Path.GetExtension(filename);
                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(filename) };
                    customiseImport?.Invoke(args);

                    var filename2 = CoreUtils.SanitiseFileName(args.FileName);
                    filename = Path.ChangeExtension(Path.Combine(path, filename2), extension);
                }

                iimporter = ImportFactory.Create(definition, entityType, importer);

                iimporter.BeforeProcess += (sender, values) =>
                {
                    if (helper != null)
                        return helper.Execute("Module", "BeforeProcess", new object[] { values });
                    return true;
                };

                iimporter.AfterProcess += (sender, item, values) =>
                {
                    var bOK = true;
                    if (helper != null)
                        bOK = helper.Execute("Module", "AfterProcess", new[] { item, values });
                    bOK = !bOK || onImport is null || onImport.Invoke(item);
                    return bOK;
                };

                var mappingDefinitions = importer.Definition;
                var mappings = Serialization.Deserialize<List<ImportMapping>>(mappingDefinitions);
                iimporter.Mappings.AddRange(mappings);
                return true;
            }
            else
            {
                return false;
            }
        }

        private bool ImportAction(CoreRow? arg)
        {
            if (arg != null)
            {
                var importer = arg.ToObject<Importer>();
                string? filename = null;

                if (CreateImporter(importer,
                    ref filename,
                    out var iimporter,
                    (args) => OnCustomiseImport?.Invoke(this, args),
                    (o) => OnImportItem?.Invoke(o) != false))
                {
                    iimporter.OnLoad += OnLoad;
                    iimporter.OnSave += OnSave;

                    string result = null;
                    bool bSuccess = false;
                    var LogFile = Path.ChangeExtension(filename, ".log");
                    
                    Progress.ShowModal("Importing Data", (progress) =>
                    {
                        iimporter.OnNotify += (o, msg) => progress.Report(msg);
                        using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
                        Progress.SetMessage("Opening File");
                        if (iimporter.Open(stream))
                        {
                            if (iimporter.ReadHeader())
                            {
                                var mismatches = iimporter.Mappings.Where(x =>
                                    !string.IsNullOrWhiteSpace(x.Field) &&
                                    !iimporter.Fields.Contains(x.Field)
                                ).Select(x => x.Field).ToArray();
                                if (!mismatches.Any())
                                {
                                    var imported = iimporter.Import();
                                    File.AppendAllLines(LogFile, iimporter.Log.ToArray());
                                    result = $"Imported {imported} records!";
                                    bSuccess = true;
                                }
                                else
                                {
                                    result = "Import Mappings do not match file headers!\n\n- " + string.Join("\n- ", mismatches);
                                }
                            }
                            else
                            {
                                result = $"Unable to Read Headers from {Path.GetFileName(filename)}";
                            }
                        }
                        else
                            result = $"Unable to Open {Path.GetFileName(filename)}";

                        iimporter.Close();
                    });
                    MessageBox.Show(result, (bSuccess ? "Success" : "Error"));
                    if (File.Exists(LogFile))
                        Process.Start(new ProcessStartInfo(LogFile) { UseShellExecute = true });
                }
            }

            return false;
        }

        private bool Importer_BeforeProcess(object sender, Dictionary<string, string> values)
        {
            return true;
            //throw new NotImplementedException();
        }

        protected override void Reload(
            Filters<Importer> criteria, Columns<Importer> columns, ref SortOrder<Importer>? sort, 
            CancellationToken token, Action<CoreTable?, Exception?> action)
        {
            criteria.Add(new Filter<Importer>(x => x.EntityName).IsEqualTo(EntityType.EntityName()));

            if (EntityID != Guid.Empty)
                criteria.Add(new Filter<Importer>(x => x.EntityID).IsEqualTo(EntityID));

            base.Reload(criteria, columns, ref sort, token, action);
        }

        public override Importer CreateItem()
        {
            var result = base.CreateItem();
            result.EntityName = EntityType.EntityName();
            result.EntityID = EntityID;
            return result;
        }

        public override bool EditItems(Importer[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
        {
            if (items.Length != 1)
            {
                MessageBox.Show("Please select a single item to edit!");
                return false;
            }

            var form = new DynamicImportForm(items[0]);
            if (form.ShowDialog() == true)
            {
                SaveItem(items[0]);
                return true;
            }

            return false;
        }
    }
}