namespace MyOffice.Services.Account
{
	using Core;
	using Core.Helpers;
	using Data.Models.Accounts;
	using Data.Models.Motions;
	using Data.Repositories.Account;
	using Data.Repositories.Currency;
	using Data.Repositories.Motion;
	using Microsoft.Extensions.Logging;
	using MyOffice.Services.Account.Domain;

	public class AccountService
	{
		private ILogger<AccountService> _logger;
		public readonly IAccountCategoryRepository _accountCategoryRepository;
		public readonly IAccountRepository _accountRepository;
		public readonly ICurrencyRepository _currencyRepository;
		public readonly IAccountAccountCategoryRepository _accountAccountCategoryRepository;
		public readonly IAccountMotionRepository _accountMotionRepository;
		public readonly IMotionRepository _motionRepository;
		public readonly IMotionGlobalRepository _motionGlobalRepository;

		public AccountService(
			ILogger<AccountService> logger,
			IAccountCategoryRepository accountCategoryRepository,
			IAccountRepository accountRepository,
			ICurrencyRepository currencyRepository,
			IAccountAccountCategoryRepository accountAccountCategoryRepository,
			IAccountMotionRepository accountMotionRepository,
			IMotionRepository motionRepository,
			IMotionGlobalRepository motionGlobalRepository
		)
		{
			_logger = logger;
			_accountCategoryRepository = accountCategoryRepository;
			_accountRepository = accountRepository;
			_currencyRepository = currencyRepository;
			_accountAccountCategoryRepository = accountAccountCategoryRepository;
			_accountMotionRepository = accountMotionRepository;
			_motionRepository = motionRepository;
			_motionGlobalRepository = motionGlobalRepository;
		}

		public List<AccountCategory> GetAllCategories(Guid userId)
		{
			return _accountCategoryRepository.GetAll(userId);
		}

		public Exec<AccountCategory, GeneralExecStatus> CategoryAdd(Guid userId, AccountCategory category)
		{
			if (category == null)
				throw new ArgumentNullException(nameof(category));

			var result = new Exec<AccountCategory, GeneralExecStatus>(GeneralExecStatus.success);

			category.Id = Guid.NewGuid();
			category.UserId = userId;

			if (!_accountCategoryRepository.Add(category))
			{
				return result.Set(GeneralExecStatus.failure);
			}

			return result.Set(category);
		}

		public Exec<AccountCategory, GeneralExecStatus> CategoryUpdate(Guid userId, Guid id, AccountCategory category)
		{
			if (category == null)
				throw new ArgumentNullException(nameof(category));

			var result = new Exec<AccountCategory, GeneralExecStatus>(GeneralExecStatus.success);

			var exists = _accountCategoryRepository.Get(userId, id);
			if (exists == null)
			{
				return result.Set(GeneralExecStatus.not_found);
			}

			exists.Name = category.Name;

			if (!_accountCategoryRepository.Update(exists))
			{
				return result.Set(GeneralExecStatus.failure);
			}

			return result.Set(exists);
		}

		public Exec<AccountCategory, AccountCategoryRemoveResult> CategoryRemove(Guid userId, Guid id)
		{
			var result = new Exec<AccountCategory, AccountCategoryRemoveResult>(AccountCategoryRemoveResult.success);

			var exists = _accountCategoryRepository.Get(userId, id);
			if (exists == null)
			{
				return result.Set(AccountCategoryRemoveResult.not_found);
			}

			if (exists.Accounts?.Any() == true)
			{
				return result.Set(AccountCategoryRemoveResult.accounts_exists);
			}
			if (!_accountCategoryRepository.Remove(exists))
			{
				return result.Set(AccountCategoryRemoveResult.failure);
			}

			return result.Set(exists);
		}

		public List<Account> GetAllAccounts(Guid userId)
		{
			return _accountRepository.GetAll(userId);
		}

		public List<Account> GetByCategory(Guid userId, Guid categoryId)
		{
			return _accountRepository.GetByCategory(userId, categoryId);
		}

		public List<AccountDetailed> GetByCategoryDetailed(Guid userId, Guid categoryId)
		{
			return _accountRepository.GetByCategoryDetailed(userId, categoryId);
		}

		public Exec<Account, AccountAddResult> AccountAdd(Guid userId, AccountAdd input)
		{
			if (input == null)
				throw new ArgumentNullException(nameof(input));

			var result = new Exec<Account, AccountAddResult>(AccountAddResult.success);

			var category = _accountCategoryRepository.Get(userId, input.CategoryId);
			if (category == null)
			{
				return result.Set(AccountAddResult.category_not_found);
			}
			var currency = _currencyRepository.GetByGlobalCurrency(userId, input.CurrencyId);
			if (currency == null)
			{
				return result.Set(AccountAddResult.currency_not_found);
			}

			var account = new Account
			{
				Id = Guid.NewGuid(),
				Name = input.Name,
				CurrencyGlobalId = currency.CurrencyGlobalId,
			};

			account.Categories = new List<AccountAccountCategory>
			{
				new()
				{
					AccountId = account.Id,
					CategoryId = category.Id,
				}
			};
			account.AccessRights = new List<AccountAccess>
			{
				new()
				{
					AccountId = account.Id,
					UserId = userId,
					IsAllowManage = true,
					IsAllowRead = true,
					IsAllowWrite = true,
				}
			};

			if (!_accountRepository.Add(account))
			{
				return result.Set(AccountAddResult.failure);
			}

			return result.Set(account);
		}

		public Exec<Account, AccountEditResult> AccountUpdate(Guid userId, Guid accountId, AccountEdit input)
		{
			if (input == null)
				throw new ArgumentNullException(nameof(input));

			var result = new Exec<Account, AccountEditResult>(AccountEditResult.success);

			AccountCategory? category = null;
			if (input.CategoryId.HasValue)
			{
				category = _accountCategoryRepository.Get(userId, input.CategoryId.Value);
				if (category == null)
				{
					return result.Set(AccountEditResult.category_not_found);
				}
			}

			var currency = _currencyRepository.GetByGlobalCurrency(userId, input.CurrencyId);
			if (currency == null)
			{
				return result.Set(AccountEditResult.currency_not_found);
			}

			var account = _accountRepository.Get(userId, accountId);
			if (account == null)
			{
				return result.Set(AccountEditResult.not_found);
			}

			account.Name = input.Name;
			if (account.CurrencyGlobalId != currency.CurrencyGlobalId)
			{
				account.CurrencyGlobalId = currency.CurrencyGlobalId;
			}

			if (category != null && account.Categories!.All(x => x.CategoryId != category.Id))
			{
				account.Categories = new List<AccountAccountCategory>
				{
					new()
					{
						AccountId = account.Id,
						CategoryId = category.Id,
					}
				};
			}

			if (!_accountRepository.Update(account))
			{
				return result.Set(AccountEditResult.failure);
			}

			return result.Set(account);
		}

		public Exec<List<AccountAccountCategory>, GeneralExecStatus> AccountCategoryRemove(Guid userId, Guid accountId, Guid categoryId)
		{
			var result = new Exec<List<AccountAccountCategory>, GeneralExecStatus>(GeneralExecStatus.success);

			var categories = _accountAccountCategoryRepository.Get(userId, accountId, categoryId);
			if (categories.Count == 0)
			{
				return result.Set(GeneralExecStatus.not_found);
			}

			foreach (var category in categories)
			{
				_accountAccountCategoryRepository.Remove(category);
			}

			return result.Set(categories);
		}

		public Exec<AccountMotion, AccountMotionAddResult> AccountMotionAdd(
			Guid userId,
			Guid accountId,
			AccountMotionAdd accountMotion
		)
		{
			if (accountMotion == null)
				throw new ArgumentNullException(nameof(accountMotion));
			if (accountMotion.Motion == null)
				throw new ArgumentNullException(nameof(accountMotion.Motion));

			var result = new Exec<AccountMotion, AccountMotionAddResult>(AccountMotionAddResult.success);

			var account = _accountRepository.Get(userId, accountId);
			if (account == null)
			{
				return result.Set(AccountMotionAddResult.account_not_found);
			}

			var motion = _motionGlobalRepository.GetByName(accountMotion.Motion);
			if (motion == null)
			{
				motion = new MotionGlobal
				{
					Id = Guid.NewGuid(),
					Name = accountMotion.Motion.Trim(),
				};
				if (_motionGlobalRepository.Add(motion))
				{
					return result.Set(AccountMotionAddResult.failure);
				}
			}

			var motionAccount = _motionRepository.GetByGlobal(userId, motion.Id);
			if (motionAccount == null)
			{
				motionAccount = new MotionAccount
				{
					CategoryId = userId,
					MotionGlobalId = motion.Id,
				};
				_motionRepository.Add(motionAccount);
			}

			var accountMotionDb = new AccountMotion
			{
				CreatedOn = DateTime.UtcNow,
				DateTime = accountMotion.Date,
				AccountId = account.Id,
				MotionId = motionAccount.Id,
				AmountPlus = accountMotion.Plus,
				AmountMinus = accountMotion.Minus,
				Description = accountMotion.Description,
			};

			if (!_accountMotionRepository.Add(accountMotionDb))
			{
				return result.Set(GeneralExecStatus.failure);

			}

			return result;
		}
	}
}
