namespace MyOffice.Services.Account;

using System.Collections.Generic;

using Core;
using Core.Extensions;
using Data.Models.Accounts;
using Domain;
using Item.Domain;

public partial class AccountService
{
	public async Task<Exec<List<MotionDto>, MotionAddStatus>> MotionAddAsync(
		Guid userId,
		Guid accountId,
		MotionAddUpdate motion,
		CancellationToken cancellationToken = default
	)
	{
		if (motion == null)
			throw new ArgumentNullException(nameof(motion));
		if (motion.Item == null)
			throw new ArgumentNullException(nameof(motion.Item));

		var result = new Exec<List<MotionDto>, MotionAddStatus>(MotionAddStatus.success);

		var account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);
		if (account == null)
		{
			return result.Set(MotionAddStatus.account_not_found);
		}

		if (!CanWrite(account, userId))
		{
			return result.Set(MotionAddStatus.forbidden);
		}

		var itemExec = await _itemService.GetOrCreateAsync(userId, motion.Item, cancellationToken);
		if (itemExec.Status != ItemGetOrAddResult.success)
		{
			return result.Set(MotionAddStatus.failure);
		}

		var motionDb = new Motion
		{
			Id = Guid.NewGuid(),
			CreatedOn = DateTime.UtcNow,
			DateTime = motion.Date,
			AccountId = account.Id,
			ItemId = itemExec.Result!.Id,
			Description = motion.Description,
			AmountPlus = motion.Plus,
			AmountMinus = motion.Minus,
		};

		if (!await _motionRepository.AddAsync(motionDb, cancellationToken))
		{
			return result.Set(MotionAddStatus.failure);
		}

		result.Set(new List<MotionDto>());
		result.Result!.Add(_mapper.Map<MotionDto>(motionDb));

		if (motion.AccountId.IsPresent() && motion.AmountBalancing != 0)
		{
			var accountBalancing = await _accountRepository.GetAsync(
				userId,
				motion.AccountId!.AsGuid(),
				cancellationToken);
			if (accountBalancing != null && CanWrite(accountBalancing, userId))
			{
				var balancingName = AccountMotionBalancing.BalancingItemName(account.Name);
				var itemBalancingExec = await _itemService.GetOrCreateAsync(
					userId,
					balancingName,
					cancellationToken);
				var (plus, minus) = AccountMotionBalancing.ResolveAmounts(motion.Plus, motion.AmountBalancing);
				var motionBalancing = new Motion
				{
					Id = Guid.NewGuid(),
					CreatedOn = DateTime.UtcNow,
					DateTime = motion.Date,
					AccountId = accountBalancing.Id,
					ItemId = itemBalancingExec.Result!.Id,
					Description = motion.Description,
					AmountPlus = plus,
					AmountMinus = minus,
				};

				if (await _motionRepository.AddAsync(motionBalancing, cancellationToken))
				{
					result.Result!.Add(_mapper.Map<MotionDto>(motionBalancing));
				}
			}
		}

		motionDb.Item = itemExec.Result!;

		return result;
	}

	public async Task<Exec<MotionDto, MotionUpdateStatus>> MotionUpdateAsync(
		Guid userId,
		Guid accountId,
		Guid motionId,
		MotionAddUpdate motion,
		CancellationToken cancellationToken = default
	)
	{
		if (motion == null)
			throw new ArgumentNullException(nameof(motion));
		if (motion.Item == null)
			throw new ArgumentNullException(nameof(motion.Item));

		var result = new Exec<MotionDto, MotionUpdateStatus>(MotionUpdateStatus.success);

		var exists = await _motionRepository.GetAsync(userId, motionId, cancellationToken);
		if (exists == null || exists.AccountId != accountId)
		{
			return result.Set(MotionUpdateStatus.not_found);
		}

		var account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);
		if (account == null)
		{
			return result.Set(MotionUpdateStatus.not_found);
		}

		if (!CanWrite(account, userId))
		{
			return result.Set(MotionUpdateStatus.forbidden);
		}

		var itemExec = await _itemService.GetOrCreateAsync(userId, motion.Item, cancellationToken);
		if (itemExec.Status != ItemGetOrAddResult.success)
		{
			return result.Set(MotionUpdateStatus.failure);
		}

		exists.Item.Id = itemExec.Result!.Id;
		exists.DateTime = motion.Date;
		exists.Description = motion.Description;
		exists.AmountPlus = motion.Plus;
		exists.AmountMinus = motion.Minus;

		if (!await _motionRepository.UpdateAsync(exists, cancellationToken))
		{
			return result.Set(MotionUpdateStatus.failure);
		}

		return result.Set(_mapper.Map<MotionDto>(exists));
	}

	public async Task<Exec<List<MotionDto>, GeneralExecStatus>> GetMotionsAsync(
		Guid userId,
		Guid accountId,
		DateTime dateFrom,
		DateTime dateTo,
		CancellationToken cancellationToken = default
	)
	{
		var result = new Exec<List<MotionDto>, GeneralExecStatus>(GeneralExecStatus.success);

		var account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);
		if (account == null)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		var motions = await _motionRepository.GetByAccountAsync(
			accountId,
			dateFrom.ToUtc(),
			dateTo.ToUtc(),
			cancellationToken);

		return result.Set(_mapper.Map<List<MotionDto>>(motions));
	}

	public async Task<Exec<MotionDto, MotionDeleteStatus>> MotionRemoveAsync(
		Guid userId,
		Guid accountId,
		Guid motionId,
		CancellationToken cancellationToken = default
	)
	{
		var result = new Exec<MotionDto, MotionDeleteStatus>(MotionDeleteStatus.success);

		var exists = await _motionRepository.GetAsync(userId, motionId, cancellationToken);
		if (exists == null || exists.AccountId != accountId)
		{
			return result.Set(MotionDeleteStatus.not_found);
		}

		var account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);
		if (account == null)
		{
			return result.Set(MotionDeleteStatus.not_found);
		}

		if (!CanWrite(account, userId))
		{
			return result.Set(MotionDeleteStatus.forbidden);
		}

		if (!await _motionRepository.RemoveAsync(exists, cancellationToken))
		{
			return result.Set(MotionDeleteStatus.failure);
		}

		return result.Set(_mapper.Map<MotionDto>(exists));
	}
}
