using CsvHelper;
using InABox.Core;
using InABox.Core.Postable;
using InABox.Scripting;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InABox.Poster.CSV;

public class CSVPosterEngine<TPostable> : PosterEngine<TPostable, ICSVPoster<TPostable>, CSVPosterSettings>
    where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
{
    private ScriptDocument? _script;
    private bool _hasCheckedScript;

    private ScriptDocument? GetScriptDocument()
    {
        if (_hasCheckedScript)
        {
            return _script;
        }

        var settings = GetSettings();
        if (settings.ScriptEnabled && !string.IsNullOrWhiteSpace(settings.Script))
        {
            var document = new ScriptDocument(settings.Script);
            document.Properties.Add(new ScriptProperty("Results", null));
            if (!document.Compile())
            {
                throw new Exception("Script failed to compile!");
            }
            _script = document;
        }
        else
        {
            _script = null;
        }

        _hasCheckedScript = true;
        return _script;
    }

    public override bool BeforePost(IDataModel<TPostable> model)
    {
        if(GetScriptDocument() is ScriptDocument script)
        {
            return script.Execute(methodname: "BeforePost", parameters: new object[] { model });
        }
        else
        {
            return Poster.BeforePost(model);
        }
    }

    protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
    {
        var settings = GetSettings();

        ICSVExport<TPostable> results;
        if (GetScriptDocument() is ScriptDocument script)
        {
            if (!script.Execute(methodname: "Process", parameters: new object[] { model }))
            {
                throw new Exception("Post Failed.");
            }

            var resultsObject = script.GetValue("Results");
            results = (resultsObject as ICSVExport<TPostable>)
                ?? throw new Exception($"Script 'Results' property expected to be ICSVExport<{typeof(TPostable)}>, got {resultsObject}");
        }
        else
        {
            results = Poster.Process(model);
        }

        var dlg = new SaveFileDialog()
        {
            FileName = settings.DefaultOutputFile,
            Filter = "CSV Files (*.csv)|*.csv"
        };

        if(dlg.ShowDialog() == true)
        {
            using var writer = new StreamWriter(dlg.FileName);
            using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
            csv.Context.RegisterClassMap(results.ClassMap.ClassMap);

            var method = typeof(CsvWriter).GetMethods()
                .Where(x => x.Name == nameof(CsvWriter.WriteRecords) && x.GetGenericArguments().Length == 1).First()
                .MakeGenericMethod(results.Type);
            method.Invoke(csv, new object?[] { results.Records });
            return results;
        }
        else
        {
            throw new PostCancelledException();
        }
    }

    public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
    {
        if (GetScriptDocument() is ScriptDocument script)
        {
            // Ignoring 'result', because this is actually the 'Results' field of the script class.
            script.Execute(methodname: "AfterPost", parameters: new object[] { model });
        }
        else
        {
            Poster.AfterPost(model, result);
        }
    }
}
