using InABox.Core;
using InABox.Core.Postable;
using InABox.Poster.Shared;
using MYOB.AccountRight.SDK;
using MYOB.AccountRight.SDK.Contracts.Version2;
using MYOB.AccountRight.SDK.Services;
using MYOB.AccountRight.SDK.Services.GeneralLedger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MYOBBaseEntity = MYOB.AccountRight.SDK.Contracts.Version2.BaseEntity;
using MYOBTaxCode = MYOB.AccountRight.SDK.Contracts.Version2.GeneralLedger.TaxCode;

namespace InABox.Poster.MYOB;

public static class MYOBPosterUtils
{
    public class QueryResult<T> : Result<PagedCollection<T>, Exception>
    {
        public QueryResult(PagedCollection<T> value) : base(value)
        {
        }

        public QueryResult(Exception error) : base(error)
        {
        }

        public static QueryResult<T> Ok(PagedCollection<T> collection) => new QueryResult<T>(collection);

        public static QueryResult<T> Error(Exception e) => new QueryResult<T>(e);
    }

    public class GetResult<T> : Result<T, Exception>
    {
        public GetResult(T value) : base(value)
        {
        }

        public GetResult(Exception error) : base(error)
        {
        }

        public static GetResult<T> Ok(T value) => new GetResult<T>(value);

        public static GetResult<T> Error(Exception e) => new GetResult<T>(e);
    }

    public static Result<PagedCollection<T>, Exception> Query<T>(
        this MutableService<T> service, MYOBConnectionData data,
        Filter<T>? filter,
        int? top = null, int? skip = null
    )
        where T : MYOBBaseEntity
    {
        var queries = new List<string>();
        if(filter is not null)
        {
            queries.Add($"$filter={Uri.EscapeDataString(filter.AsOData())}");
        }
        if (top.HasValue)
        {
            queries.Add($"$top={top.Value}");
        }
        if (skip.HasValue)
        {
            queries.Add($"$skip={skip.Value}");
        }

        try
        {
            var values = service.GetRange(data.CompanyFile, string.Join('&', queries), data.CompanyFileCredentials);
            return QueryResult<T>.Ok(values);
        }
        catch(ApiCommunicationException e)
        {
            return QueryResult<T>.Error(new Exception(FormatApiException(e), e));
        }
        catch(Exception e)
        {
            return QueryResult<T>.Error(e);
        }
    }

    public static Result<T, Exception> Save<T>(
        this MutableService<T> service, MYOBConnectionData data,
        T entity)
        where T : MYOBBaseEntity
    {
        try
        {
            if(entity.UID == Guid.Empty)
            {
                return Result.Ok(service.InsertEx(data.CompanyFile, entity, data.CompanyFileCredentials));
            }
            else
            {
                return Result.Ok(service.UpdateEx(data.CompanyFile, entity, data.CompanyFileCredentials));
            }
        }
        catch(ApiCommunicationException e)
        {
            return Result.Error(new Exception(FormatApiException(e), e));
        }
        catch(Exception e)
        {
            return Result.Error(e);
        }
    }

    public static GetResult<T> Get<T>(this MutableService<T> service, MYOBConnectionData data, Guid id)
        where T : MYOBBaseEntity
    {
        try
        {
            var value = service.Get(data.CompanyFile, id, data.CompanyFileCredentials);
            return GetResult<T>.Ok(value);
        }
        catch(ApiCommunicationException e)
        {
            return GetResult<T>.Error(new Exception(FormatApiException(e), e));
        }
        catch(Exception e)
        {
            return GetResult<T>.Error(e);
        }
    }

    public static TService CreateService<TService, TEntity>(this MYOBConnectionData data)
        where TService : MutableService<TEntity>
        where TEntity : MYOBBaseEntity
    {
        return (Activator.CreateInstance(typeof(TService), data.Configuration, null, data.AuthKey) as TService)!;
    }

    public static Result<Guid, Exception> GetUID<TService, TEntity>(this MYOBConnectionData data, Filter<TEntity> filter)
        where TService : MutableService<TEntity>
        where TEntity : MYOBBaseEntity
    {
        var service = data.CreateService<TService, TEntity>();
        var result = service.Query(data, filter, top: 1);
        if(!result.Get(out var items, out var error))
        {
            return Result.Error(error);
        }
        if(items.Items.Length == 0)
        {
            return Result.Ok(Guid.Empty);
        }
        return Result.Ok(items.Items[0].UID);
    }

    public static string FormatApiException(ApiCommunicationException e)
    {
        if(e.Errors is not null && e.Errors.Count > 0)
        {
            var message = string.Join('\n', e.Errors.Select(x =>
            {
                return $"{x.Name}: {x.Message} ({x.AdditionalDetails})";
            }));
            return message;
        }
        else
        {
            return e.Message;
        }
    }

    public static Result<Guid, Exception> GetMYOBTaxCodeUID(this MYOBConnectionData data, string code)
        => data.GetUID<TaxCodeService, MYOBTaxCode>(new Filter<MYOBTaxCode>(x => x.Code).IsEqualTo(code));

    public static Result<Guid, Exception> GetDefaultTaxCode(MYOBConnectionData data, MYOBGlobalPosterSettings settings)
    {
        if (settings.DefaultTaxCode.IsNullOrWhiteSpace())
        {
            throw new MissingSettingException<MYOBGlobalPosterSettings>(x => x.DefaultTaxCode);
        }
        else if(data.GetMYOBTaxCodeUID(settings.DefaultTaxCode).Get(out var taxID, out var error))
        {
            if (taxID == Guid.Empty)
            {
                return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code '{settings.DefaultTaxCode}'"));
            }
            return Result.Ok(taxID);
        }
        else
        {
            CoreUtils.LogException("", error, $"Failed to find TaxCode in MYOB with code '{settings.DefaultTaxCode}'");
            return Result.Error(new Exception($"Failed to find TaxCode in MYOB with code '{settings.DefaultTaxCode}': {error.Message}", error));
        }
    }

    public static Result<Guid, Exception> GetDefaultTaxCode<TPostable, TSettings>(this IMYOBPoster<TPostable, TSettings> poster)
        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
        where TSettings : MYOBPosterSettings
    {
        return GetDefaultTaxCode(poster.ConnectionData, poster.GlobalSettings);
    }

    public static Result<Exception> WrapScript<TPostable, TSettings>(
        this IMYOBPoster<TPostable, TSettings> poster,
        string methodname,
        params object?[] parameters
    )
        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
        where TSettings : MYOBPosterSettings
    {
        if (poster.Script is null) return Result.Ok();

        try
        {
            if(poster.Script.Execute(methodname: methodname, parameters: parameters))
            {
                return Result.Ok();
            }
            else
            {
                throw new PostCancelledException();
            }
        }
        catch(TargetInvocationException e)
        {
            if(e.InnerException is not null)
            {
                return Result.Error(e.InnerException);
            }
            else
            {
                return Result.Error<Exception>(e);
            }
        }
        catch(Exception e)
        {
            return Result.Error(e);
        }
    }
}
