using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media.Imaging;
using InABox.Core;
using InABox.Scripting;
using InABox.WPF;
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();

        public DynamicImportGrid()
        {
            Options.AddRange(DynamicGridOption.RecordCount, DynamicGridOption.ShowHelp);
            ActionColumns.Add(new DynamicImageColumn(ImportImage, ImportAction));
            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);
        }

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

        private int[] ExtractColumnWidths(string widths)
        {
            if (string.IsNullOrWhiteSpace(widths))
                return new[] { 1024 };

            try
            {
                return widths.Split(',').Select(x => int.Parse(x)).ToArray();
            }
            catch
            {
                return new[] { 1024 };
            }
        }

        private bool ImportAction(CoreRow? arg)
        {
            if (arg != null)
            {
                var definition =
                    ImportFactory.Definitions.FirstOrDefault(x => x.Description.Equals(arg.Get<Importer, string>(c => c.ImporterDescription)));
                if (definition != null)
                {
                    ScriptDocument? helper = null;
                    try
                    {
                        var script = arg.Get<Importer, string>(x => x.Script);
                        if (!string.IsNullOrWhiteSpace(script))
                        {
                            helper = new ScriptDocument(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));
                    }

                    var fullfilename = arg.Get<Importer, string>(x => x.FileName);
                    var path = Path.GetDirectoryName(fullfilename);
                    var extension = Path.GetExtension(fullfilename);
                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(fullfilename) };
                    OnCustomiseImport?.Invoke(this, args);
                    var filename = CoreUtils.SanitiseFileName(args.FileName);
                    var dlg = new OpenFileDialog();
                    dlg.Filter = definition.Filter;
                    dlg.FileName = Path.Combine(path, filename) + (extension.StartsWith(".") ? "" : ".") + extension;
                    if (!string.IsNullOrWhiteSpace(dlg.FileName) && Directory.Exists(Path.GetDirectoryName(dlg.FileName)))
                        dlg.InitialDirectory = Path.GetDirectoryName(dlg.FileName);
                    if (dlg.ShowDialog() == true)
                    {
                        Progress.Show("Importing Data");
                        var importer = ImportFactory.Create(definition, EntityType);
                        if (importer is IFixedWidthImporter)
                            ((IFixedWidthImporter)importer).ColumnWidths = ExtractColumnWidths(arg.Get<Importer, string>(x => x.ColumnWidths));

                        try
                        {
                            importer.HasHeader = arg.Get<Importer, bool>(x => x.HasHeader);
                        }
                        catch
                        {
                            importer.HasHeader = true;
                        }

                        try
                        {
                            importer.HeaderRow = Math.Max(1, arg.Get<Importer, int>(x => x.HeaderRows));
                        }
                        catch
                        {
                            importer.HeaderRow = 1;
                        }

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

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

                        importer.OnSave += (sender, entity) => OnSave?.Invoke(sender, entity);
                        importer.OnLoad += (sender, type, fields, id) => OnLoad(sender, type, fields, id); 
                        
                        importer.OnNotify += (o, m) => { Progress.SetMessage(m); };

                        var settings = arg.Get<Importer, string>(c => c.Definition);
                        var mappings = Serialization.Deserialize<List<ImportMapping>>(settings);
                        importer.Mappings.AddRange(mappings);
                        using (var stream = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
                        {
                            Progress.SetMessage("Opening File");
                            if (importer.Open(stream))
                            {
                                if (importer.ReadHeader())
                                {
                                    var mismatches = mappings.Where(x =>
                                        !string.IsNullOrWhiteSpace(x.Field) &&
                                        !importer.Fields.Contains(x.Field)
                                    ).Select(x => x.Field).ToArray();
                                    if (!mismatches.Any())
                                    {
                                        var imported = importer.Import();
                                        Progress.Close();
                                        MessageBox.Show(string.Format("Imported {0} records!", imported));
                                        var LogFile = Path.ChangeExtension(dlg.FileName, ".log");
                                        File.AppendAllLines(LogFile, importer.Log.ToArray());
                                        Process.Start(new ProcessStartInfo(LogFile) { UseShellExecute = true });
                                    }
                                    else
                                    {
                                        Progress.Close();
                                        MessageBox.Show("Import Mappings do not match file headers!\n\n- " + string.Join("\n- ", mismatches),
                                            "Import Failed");
                                    }
                                }
                                else
                                {
                                    Progress.Close();
                                    MessageBox.Show("Unable to Read Headers from {0}", Path.GetFileName(dlg.FileName));
                                }
                            }
                            else
                            {
                                Progress.Close();
                                MessageBox.Show("Unable to Open {0}", Path.GetFileName(dlg.FileName));
                            }

                            importer.Close();
                        }
                    }
                }
            }

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

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