using InABox.Core;
using InABox.Core.Postable;
using InABox.DynamicGrid;
using InABox.Poster.Shared;
using Microsoft.Web.WebView2.Wpf;
using MYOB.AccountRight.SDK;
using MYOB.AccountRight.SDK.Contracts;
using MYOB.AccountRight.SDK.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace InABox.Poster.MYOB;

public class MYOBConnectionData(ApiConfiguration configuration, CompanyFileService cfService, IOAuthKeyService authKey)
{
    public ApiConfiguration Configuration { get; set; } = configuration;

    public CompanyFileService CompanyFileService { get; set; } = cfService;

    public CompanyFile? CompanyFile { get; set; }

    public CompanyFileCredentials? CompanyFileCredentials { get; set; }

    public CompanyFileWithResources? ActiveCompanyFile { get; set; }

    public IOAuthKeyService AuthKey { get; set; } = authKey;
}

public static partial class MYOBPosterEngine
{
    internal static readonly string DEV_KEY = "f3b27f88-2ef9-4d8e-95c1-d0a045d0afee";
    internal static readonly string SECRET_KEY = "ksm0e8yo6oumcPb63A8cduaN";
    internal const string AUTH_URL = "https://secure.myob.com/oauth2/account/authorize";
    internal const string AUTH_SCOPE = "CompanyFile";
    internal const string REDIRECT_URL = "http://desktop";

    private static MYOBConnectionData? _connectionData;

    public static bool GetAuthorisationCode(IApiConfiguration config, out string? code)
    {
        var url = $"{AUTH_URL}?client_id={config.ClientId}&redirect_uri={HttpUtility.UrlEncode(config.RedirectUrl)}&scope={AUTH_SCOPE}&response_type=code";

        var window = new Window
        {
            Width = 800,
            Height = 600,
            Title = "Sign in to MYOB"
        };

        string? resultCode = null;

        var view = new WebView2
        {
        };
        view.NavigationCompleted += (o, e) =>
        {
            view.ExecuteScriptAsync("document.documentElement.innerHTML;").ContinueWith(task =>
            {
                if (task.Result is null) return;

                var str = Serialization.Deserialize<string>(task.Result);
                if (str is null) return;

                var match = CodeRegex().Match(str);
                if (!match.Success) return;

                resultCode = match.Groups[1].Value;
                window.Dispatcher.BeginInvoke(() =>
                {
                    if(window.DialogResult is null)
                    {
                        window.DialogResult = true;
                        window.Close();
                    }
                });
            });
        };
        view.Source = new Uri(url);
        window.Content = view;

        if(window.ShowDialog() == true)
        {
            code = resultCode;
            return true;
        }
        else
        {
            code = null;
            return false;
        }
    }

    public static MYOBConnectionData? GetConnectionDataOrNull()
    {
        return _connectionData;
    }

    public static MYOBConnectionData GetConnectionData()
    {
        if(_connectionData is MYOBConnectionData data)
        {
            return _connectionData;
        }

        var configuration = new ApiConfiguration(DEV_KEY, SECRET_KEY, REDIRECT_URL);
        var authService = new OAuthService(configuration);

        if(!GetAuthorisationCode(configuration, out var code))
        {
            throw new PostCancelledException();
        }
        if(code is null)
        {
            throw new PostFailedMessageException("No authorisation code was received.");
        }

        var keystore = new SimpleOAuthKeyService
        {
            OAuthResponse = authService.GetTokens(code)
        };

        var cfService = new CompanyFileService(configuration, null, keystore);

        _connectionData = new MYOBConnectionData(configuration, cfService, keystore);

        return _connectionData;
    }

    [GeneratedRegex("code=(.*)<")]
    private static partial Regex CodeRegex();
}

public abstract class MYOBPosterEngine<TPostable, TPoster, TSettings> :
    BasePosterEngine<TPostable, TPoster, TSettings>,
    IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>

    where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
    where TSettings : MYOBPosterSettings, new()
    where TPoster : class, IMYOBPoster<TPostable, TSettings>
{
    private MYOBGlobalPosterSettings GetGlobalSettings() =>
        (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).GetGlobalSettings();

    private void SaveGlobalSettings(MYOBGlobalPosterSettings settings) =>
        (this as IGlobalSettingsPosterEngine<TPoster, MYOBGlobalPosterSettings>).SaveGlobalSettings(settings);

    protected override TPoster CreatePoster()
    {
        var poster = base.CreatePoster();
        poster.Script = GetScriptDocument();
        return poster;
    }

    public override bool BeforePost(IDataModel<TPostable> model)
    {
        return Poster.BeforePost(model);
    }

    protected void LoadConnectionData()
    {
        var data = MYOBPosterEngine.GetConnectionData();

        var globalSettings = GetGlobalSettings();
        if(data.CompanyFile is null || data.CompanyFile.Id != globalSettings.CompanyFile.ID)
        {
            CompanyFile? file;
            if(globalSettings.CompanyFile.ID == Guid.Empty)
            {
                file = MYOBCompanyFileSelectionDialog.SelectCompanyFile();
                if(file is null)
                {
                    throw new PostCancelledException();
                }
                else
                {
                    globalSettings.CompanyFile.ID = file.Id;
                    globalSettings.CompanyFile.Name = file.Name;

                    SaveGlobalSettings(globalSettings);
                    globalSettings.CommitChanges();
                }
            }

            if(!globalSettings.NoCredentials && globalSettings.CompanyFileUserID.IsNullOrWhiteSpace())
            {
                var credentials = new MYOBCompanyFileCredentials
                {
                    UserID = globalSettings.CompanyFileUserID,
                    Password = globalSettings.CompanyFilePassword,
                    NoCredentials = globalSettings.NoCredentials
                };
                if (DynamicGridUtils.EditObject(credentials, customiseGrid: grid =>
                {
                    grid.OnValidate += (grid, items, errors) =>
                    {
                        var item = items.FirstOrDefault();
                        if (item is null) return;

                        if(!item.NoCredentials && item.UserID.IsNullOrWhiteSpace())
                        {
                            errors.Add("[UserID] cannot be blank");
                        }
                    };
                }))
                {
                    globalSettings.NoCredentials = credentials.NoCredentials;
                    globalSettings.CompanyFileUserID = credentials.UserID;
                    globalSettings.CompanyFilePassword = credentials.Password;

                    SaveGlobalSettings(globalSettings);
                    globalSettings.CommitChanges();
                }
                else
                {
                    throw new PostCancelledException();
                }
            }

            var companyFile = data.CompanyFileService.GetRange().FirstOrDefault(x => x.Id == globalSettings.CompanyFile.ID);
            var fileCredentials = new CompanyFileCredentials(globalSettings.CompanyFileUserID, globalSettings.CompanyFilePassword);
            data.CompanyFile = companyFile;
            data.CompanyFileCredentials = fileCredentials;
            // data.ActiveCompanyFile = data.CompanyFileService.Get(companyFile, fileCredentials);
        }
        Poster.ConnectionData = data;
    }
    
    protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
    {
        LoadConnectionData();
        return Poster.Process(model);
    }

    public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
    {
    }
}
