namespace MyOffice.Services.Account;

using MyOffice.Core;
using MyOffice.Core.Extensions;
using MyOffice.Data.Models.Accounts;
using MyOffice.Services.Account.Domain;

public partial class AccountService
{
	public async Task<List<AccountDto>> GetAllAccountsAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		var accounts = await _accountRepository.GetAllAsync(userId, cancellationToken);

		return accounts.ToDto<AccountDto>(_mapper);
	}

	public async Task<List<AccountDto>> GetByCategoryAsync(Guid userId, Guid categoryId, CancellationToken cancellationToken = default)
	{
		var accounts = await _accountRepository.GetByCategoryAsync(userId, categoryId, cancellationToken);

		return accounts.ToDto<AccountDto>(_mapper);
	}

	public async Task<List<AccountDto>> FindAccountsAsync(Guid userId, string term, CancellationToken cancellationToken = default)
	{
		var accounts = await _accountRepository.FindAccountsAsync(userId, term, cancellationToken);

		return accounts.ToDto<AccountDto>(_mapper);
	}

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

	public async Task<List<AccountDetailedDto>> GetByCategoryDetailedAsync(
		Guid userId,
		Guid categoryId,
		CancellationToken cancellationToken = default
	)
	{
		var list = await _accountRepository.GetByCategoryDetailedAsync(userId, categoryId, cancellationToken);
		return list.ToDto<AccountDetailedDto>(_mapper);
	}

	public Exec<AccountDetailedDto, GeneralExecStatus> GetByIdDetailed(Guid userId, Guid id)
	{
		var result = new Exec<AccountDetailedDto, GeneralExecStatus>(GeneralExecStatus.success);

		var account = _accountRepository.GetByIdDetailed(userId, id);
		if (account == null)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		return result.Set(account.ToDto<AccountDetailedDto>(_mapper));;
	}

	public async Task<Exec<AccountDetailedDto, GeneralExecStatus>> GetByIdDetailedAsync(
		Guid userId,
		Guid id,
		CancellationToken cancellationToken = default
	)
	{
		var result = new Exec<AccountDetailedDto, GeneralExecStatus>(GeneralExecStatus.success);

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

		return result.Set(account.ToDto<AccountDetailedDto>(_mapper));
	}

	public async Task<Exec<AccountDto, AccountAddStatus>> AccountAddAsync(
		Guid userId,
		AccountAdd input,
		CancellationToken cancellationToken = default
	)
	{
		if (input == null)
			throw new ArgumentNullException(nameof(input));

		var result = new Exec<AccountDto, AccountAddStatus>(AccountAddStatus.success);

		var category = await _accountCategoryRepository.GetAsync(userId, input.CategoryId, cancellationToken);
		if (category == null)
		{
			return result.Set(AccountAddStatus.category_not_found);
		}
		var currency = await _currencyRepository.GetByGlobalCurrencyAsync(userId, input.CurrencyId, cancellationToken);
		if (currency == null)
		{
			return result.Set(AccountAddStatus.currency_not_found);
		}

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

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

		if (!await _accountRepository.AddAsync(account, cancellationToken))
		{
			return result.Set(AccountAddStatus.failure);
		}
		// TODO: Add category refactoring
		if (!await _accountAccountCategoryRepository.AddAsync(account.Categories.FirstOrDefault()!, cancellationToken))
		{
			return result.Set(AccountAddStatus.failure);
		}
		// TODO: Add access refactoring
		if (!await _accountAccessRepository.AddAsync(account.AccessRights.FirstOrDefault()!, cancellationToken))
		{
			return result.Set(AccountAddStatus.failure);
		}

		// TODO: Add account update AccessRights ???
		var access = account.AccessRights!.FirstOrDefault(x => x.UserId == userId);
		if (access != null && access.Type.ToString() != input.Type)
		{
			if (Enum.TryParse<AccountAccessTypeEnum>(input.Type, out var enumType))
			{
				access.Type = enumType;
				await _accountAccessRepository.UpdateAsync(access, cancellationToken);
			}
		}

		return result.Set(account.ToDto<AccountDto>(_mapper));
	}

	public async Task<Exec<AccountDto, GeneralExecStatus>> AccountDeleteAsync(
		Guid userId,
		string id,
		CancellationToken cancellationToken = default
	)
	{
		var result = new Exec<AccountDto, GeneralExecStatus>(GeneralExecStatus.success);

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

		if (!CanManage(account, userId))
		{
			return result.Set(GeneralExecStatus.forbidden);
		}

		if (account.Motions!.Any())
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		await _accountRepository.DeleteAsync(account, cancellationToken);
		result.Set(account.ToDto<AccountDto>(_mapper));

		return result;
	}

	public async Task<Exec<AccountDto, AccountEditStatus>> AccountUpdateAsync(
		Guid userId,
		Guid accountId,
		AccountEdit input,
		CancellationToken cancellationToken = default
	)
	{
		if (input == null)
			throw new ArgumentNullException(nameof(input));

		var result = new Exec<AccountDto, AccountEditStatus>(AccountEditStatus.success);

		AccountCategory? category = null;
		if (input.CategoryId.HasValue)
		{
			category = await _accountCategoryRepository.GetAsync(userId, input.CategoryId.Value, cancellationToken);
			if (category == null)
			{
				return result.Set(AccountEditStatus.category_not_found);
			}
		}

		var currency = await _currencyRepository.GetByGlobalCurrencyAsync(userId, input.CurrencyId, cancellationToken);
		if (currency == null)
		{
			return result.Set(AccountEditStatus.currency_not_found);
		}

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

		if (!CanManage(account, userId))
		{
			return result.Set(AccountEditStatus.forbidden);
		}

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

		AccountAccountCategory? addedLink = null;
		if (category != null && account.Categories!.All(x => x.CategoryId != category.Id))
		{
			addedLink = new AccountAccountCategory
			{
				AccountId = account.Id,
				CategoryId = category.Id,
				Category = category,
			};
			await _accountAccountCategoryRepository.AddAsync(addedLink, cancellationToken);
		}

		if (!await _accountRepository.UpdateAsync(account, cancellationToken))
		{
			return result.Set(AccountEditStatus.failure);
		}

		var access = account.AccessRights!.FirstOrDefault(x => x.UserId == userId);
		if (access != null && access.Type.ToString() != input.Type)
		{
			if (Enum.TryParse<AccountAccessTypeEnum>(input.Type, out var enumType))
			{
				access.Type = enumType;
				await _accountAccessRepository.UpdateAsync(access, cancellationToken);
			}
		}

		// AddAsync detaches the link and does not refresh Account.Categories — keep response in sync.
		if (addedLink != null && (account.Categories?.All(x => x.CategoryId != addedLink.CategoryId) ?? true))
		{
			account.Categories = (account.Categories ?? Enumerable.Empty<AccountAccountCategory>())
				.Append(addedLink)
				.ToList();
		}

		return result.Set(account.ToDto<AccountDto>(_mapper));
	}
}
