using Grand.Business.Checkout.Services.Payments;
using Grand.Business.Core.Enums.Checkout;
using Grand.Business.Core.Interfaces.Checkout.Payments;
using Grand.Business.Core.Interfaces.Common.Configuration;
using Grand.Domain.Orders;
using Grand.Domain.Payments;
using Grand.SharedKernel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Grand.Business.Checkout.Tests.Services.Payments;

[TestClass]
public class PaymentServiceTests
{
    private Mock<IPaymentProvider> _paymentProviderMock;
    private IPaymentService _paymentService;
    private PaymentSettings _paymentSettings;
    private Mock<ISettingService> _settingService;

    [TestInitialize]
    public void TestInitialize()
    {
        _paymentSettings = new PaymentSettings {
            ActivePaymentProviderSystemNames = [
                "Payments.TestMethod"
            ]
        };
        _settingService = new Mock<ISettingService>();
        _paymentProviderMock = new Mock<IPaymentProvider>();
        _paymentService = new PaymentService(_paymentSettings,
            new List<IPaymentProvider> { _paymentProviderMock.Object }, _settingService.Object);
    }

    [TestMethod]
    public void LoadPaymentMethodBySystemName_ReturnPeyment()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var systemName = "systemName";
        var result = _paymentService.LoadPaymentMethodBySystemName(systemName);
        Assert.AreEqual(result, _paymentProviderMock.Object);
    }

    [TestMethod]
    public void GetRestrictedCountryIds()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var expectedResult = new List<string> { "1", "2", "3", "4" };
        var expectedKey = "PaymentMethodRestictions.systemName";
        _settingService.Setup(s => s.GetSettingByKey<PaymentRestrictedSettings>(It.IsAny<string>(), null, ""))
            .Returns(() => new PaymentRestrictedSettings { Ids = expectedResult });

        var result = _paymentService.GetRestrictedCountryIds(_paymentProviderMock.Object);
        Assert.IsTrue(expectedResult.SequenceEqual(result));
        _settingService.Verify(s => s.GetSettingByKey<PaymentRestrictedSettings>(expectedKey, null, ""), Times.Once);
    }

    [TestMethod]
    public void GetRestrictedCountryIds_ReturnEmptyList()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var expectedKey = "PaymentMethodRestictions.systemName";
        _settingService.Setup(s => s.GetSettingByKey<PaymentRestrictedSettings>(It.IsAny<string>(), null, ""))
            .Returns(() => null);

        var result = _paymentService.GetRestrictedCountryIds(_paymentProviderMock.Object);
        Assert.IsTrue(result.Count == 0);
        _settingService.Verify(s => s.GetSettingByKey<PaymentRestrictedSettings>(expectedKey, null, ""), Times.Once);
    }

    [TestMethod]
    public async Task SaveRestictedCountryIds_InvokeSettingsService()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var countryIds = new List<string> { "1", "2", "3", "4" };
        var expectedKey = "PaymentMethodRestictions.systemName";

        await _paymentService.SaveRestrictedCountryIds(_paymentProviderMock.Object, countryIds);
        _settingService.Verify(
            s => s.SetSetting(expectedKey, It.IsAny<PaymentRestrictedSettings>(), It.IsAny<string>(), It.IsAny<bool>()),
            Times.Once);
    }

    [TestMethod]
    public async Task ProcessPayment_OrderTotalZero_ReturnPaidPaymentStatus()
    {
        var request = new PaymentTransaction {
            TransactionAmount = 0
        };
        var response = await _paymentService.ProcessPayment(request);
        Assert.IsTrue(response.NewPaymentTransactionStatus == TransactionStatus.Paid);
    }

    [TestMethod]
    public async Task ProcessPayment_InvokeProcessPaymentFromPaymentMethod()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var request = new PaymentTransaction { PaymentMethodSystemName = "systemName", TransactionAmount = 500 };


        await _paymentService.ProcessPayment(request);
        _paymentProviderMock.Verify(m => m.ProcessPayment(request), Times.Once);
    }

    [TestMethod]
    public void ProcessPayment_NotFoundPaymentMethod_ThrowException()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName2");
        var request = new PaymentTransaction { PaymentMethodSystemName = "systemName", TransactionAmount = 500 };
        Assert.ThrowsExceptionAsync<GrandException>(async () => await _paymentService.ProcessPayment(request));
    }

    [TestMethod]
    public async Task PostProcessPayment_InvokePostProccessFromPaymentMethod()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var request = new PaymentTransaction {
            PaymentMethodSystemName = "systemName", TransactionAmount = 500,
            TransactionStatus = TransactionStatus.Authorized
        };
        await _paymentService.PostProcessPayment(request);
        _paymentProviderMock.Verify(m => m.PostProcessPayment(request), Times.Once);
    }

    [TestMethod]
    public void PostProcessPayment_NotFoundPaymentMethod_ThrowException()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        var request = new PaymentTransaction {
            PaymentMethodSystemName = "systemName2", TransactionAmount = 500,
            TransactionStatus = TransactionStatus.Authorized
        };
        Assert.ThrowsExceptionAsync<GrandException>(async () => await _paymentService.PostProcessPayment(request),
            "Payment method couldn't be loaded");
    }

    [TestMethod]
    public async Task CanRePostRedirectPayment_ReturnExpectedResult()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        _paymentProviderMock.Setup(c => c.CanRePostRedirectPayment(It.IsAny<PaymentTransaction>())).ReturnsAsync(true);
        var request = new PaymentTransaction { PaymentMethodSystemName = "systemName" };
        _paymentSettings.AllowRePostingPayments = false;
        Assert.IsFalse(await _paymentService.CanRePostRedirectPayment(request));
        _paymentSettings.AllowRePostingPayments = true;
        _paymentProviderMock.Setup(c => c.PaymentMethodType).Returns(PaymentMethodType.Other);
        Assert.IsFalse(await _paymentService.CanRePostRedirectPayment(request));
        request.TransactionStatus = TransactionStatus.Canceled;
        Assert.IsFalse(await _paymentService.CanRePostRedirectPayment(request));
        request.TransactionStatus = TransactionStatus.Pending;
        Assert.IsFalse(await _paymentService.CanRePostRedirectPayment(request));
        _paymentProviderMock.Setup(c => c.PaymentMethodType).Returns(PaymentMethodType.Redirection);
        request.TransactionStatus = TransactionStatus.Pending;
        Assert.IsTrue(await _paymentService.CanRePostRedirectPayment(request));
    }

    [TestMethod]
    public async Task GetAdditionalHandlingFee_ReturnExpectedResults()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        _paymentProviderMock.Setup(c => c.GetAdditionalHandlingFee(It.IsAny<IList<ShoppingCartItem>>()))
            .ReturnsAsync(100);
        var cart = new List<ShoppingCartItem> {
            new(),
            new()
        };
        Assert.AreEqual(100, await _paymentService.GetAdditionalHandlingFee(cart, "systemName"));
    }

    [TestMethod]
    public async Task GetAdditionalHandlingFee_SystemnameNull_ReturnZero()
    {
        _paymentProviderMock.Setup(c => c.SystemName).Returns("systemName");
        _paymentProviderMock.Setup(c => c.GetAdditionalHandlingFee(It.IsAny<IList<ShoppingCartItem>>()))
            .ReturnsAsync(100);
        var cart = new List<ShoppingCartItem> {
            new(),
            new()
        };
        Assert.AreEqual(0, await _paymentService.GetAdditionalHandlingFee(cart, null));
    }
}