using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using MyOffice.Data.Models.Accounts;
using MyOffice.Data.Models.Items;
using MyOffice.Data.Repositories.Account;
using MyOffice.Data.Repositories.Currency;
using MyOffice.Data.Repositories.Item;
using MyOffice.Services.Account;
using MyOffice.Services.Account.Domain;
using MyOffice.Services.Identity;
using MyOffice.Services.Item;

using AccountEntity = MyOffice.Data.Models.Accounts.Account;

namespace MyOffice.Tests.Accounts;

public class MotionAddAclTests
{
	[Fact]
	public async Task MotionAddAsync_returns_forbidden_without_write_access()
	{
		var userId = Guid.NewGuid();
		var accountId = Guid.NewGuid();
		var account = new AccountEntity
		{
			Id = accountId,
			Name = "Cash",
			AccessRights =
			[
				new AccountAccess
				{
					UserId = userId,
					IsAllowRead = true,
					IsAllowWrite = false,
					IsAllowManage = false,
				}
			]
		};

		var accountRepo = new Mock<IAccountRepository>();
		accountRepo
			.Setup(x => x.GetAsync(userId, accountId, It.IsAny<CancellationToken>()))
			.ReturnsAsync(account);

		var service = CreateService(accountRepo.Object);

		var exec = await service.MotionAddAsync(
			userId,
			accountId,
			new MotionAddUpdate
			{
				Date = DateTime.UtcNow,
				Item = "Coffee",
				Plus = 0,
				Minus = 10,
			});

		Assert.Equal(MotionAddStatus.forbidden, exec.Status);
	}

	[Fact]
	public async Task MotionAddAsync_skips_balancing_when_counterparty_not_writable()
	{
		var userId = Guid.NewGuid();
		var primaryId = Guid.NewGuid();
		var balancingId = Guid.NewGuid();

		var primary = new AccountEntity
		{
			Id = primaryId,
			Name = "Cash",
			AccessRights =
			[
				new AccountAccess { UserId = userId, IsAllowRead = true, IsAllowWrite = true }
			]
		};
		var balancing = new AccountEntity
		{
			Id = balancingId,
			Name = "Bank",
			AccessRights =
			[
				new AccountAccess { UserId = userId, IsAllowRead = true, IsAllowWrite = false }
			]
		};

		var accountRepo = new Mock<IAccountRepository>();
		accountRepo
			.Setup(x => x.GetAsync(userId, primaryId, It.IsAny<CancellationToken>()))
			.ReturnsAsync(primary);
		accountRepo
			.Setup(x => x.GetAsync(userId, balancingId, It.IsAny<CancellationToken>()))
			.ReturnsAsync(balancing);

		var motionRepo = new Mock<IMotionRepository>();
		motionRepo
			.Setup(x => x.AddAsync(It.IsAny<Motion>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync(true);

		var service = CreateService(accountRepo.Object, motionRepo.Object);

		var exec = await service.MotionAddAsync(
			userId,
			primaryId,
			new MotionAddUpdate
			{
				Date = DateTime.UtcNow,
				Item = "Transfer",
				Plus = 0,
				Minus = 25,
				AccountId = balancingId.ToString("N"),
				AmountBalancing = 25,
			});

		Assert.Equal(MotionAddStatus.success, exec.Status);
		Assert.NotNull(exec.Result);
		Assert.Single(exec.Result!);
		motionRepo.Verify(x => x.AddAsync(It.IsAny<Motion>(), It.IsAny<CancellationToken>()), Times.Once);
	}

	private static ItemService CreateItemServiceStub()
	{
		var nextId = 1;
		var itemRepo = new Mock<IItemRepository>();
		var itemGlobalRepo = new Mock<IItemGlobalRepository>();

		itemGlobalRepo
			.Setup(x => x.GetByNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync((ItemGlobal?)null);
		itemGlobalRepo
			.Setup(x => x.AddAsync(It.IsAny<ItemGlobal>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync(true);
		itemRepo
			.Setup(x => x.GetByGlobalAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync((Item?)null);
		itemRepo
			.Setup(x => x.AddAsync(It.IsAny<Item>(), It.IsAny<CancellationToken>()))
			.ReturnsAsync(true)
			.Callback<Item, CancellationToken>((i, _) => i.Id = nextId++);

		return new ItemService(
			Mock.Of<IItemCategoryRepository>(),
			itemRepo.Object,
			itemGlobalRepo.Object,
			new MapperConfiguration(_ => { }, NullLoggerFactory.Instance).CreateMapper());
	}

	private static AccountService CreateService(
		IAccountRepository accountRepository,
		IMotionRepository? motionRepository = null)
	{
		var mapper = new MapperConfiguration(
			cfg => cfg.AddProfile<MotionDtoProfile>(),
			NullLoggerFactory.Instance).CreateMapper();

		return new AccountService(
			Mock.Of<ILogger<AccountService>>(),
			mapper,
			Mock.Of<IAccountCategoryRepository>(),
			Mock.Of<IAccountAccessRepository>(),
			Mock.Of<IAccountAccessInviteRepository>(),
			accountRepository,
			Mock.Of<ICurrencyRepository>(),
			Mock.Of<IAccountAccountCategoryRepository>(),
			motionRepository ?? Mock.Of<IMotionRepository>(),
			Mock.Of<IItemRepository>(),
			Mock.Of<IItemGlobalRepository>(),
			CreateItemServiceStub(),
			Mock.Of<IContextProvider>());
	}
}
