namespace MyOffice.Services.Account;

using System.Collections.Generic;

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

public partial class AccountService
{
	public async Task<Exec<AccountAccessInviteDto, AccessInviteStatus>> AccessInviteAsync(
		Guid userId,
		Guid accountId,
		string email,
		bool isAllowWrite,
		CancellationToken cancellationToken = default)
	{
		if (email == null)
			throw new ArgumentNullException(nameof(email));

		var result = new Exec<AccountAccessInviteDto, AccessInviteStatus>(AccessInviteStatus.success);

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

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

		if (account.AccessRights!.Any(x => x.User!.Email.EqualsIgnoreCase(email)))
		{
			return result.Set(AccessInviteStatus.access_exists);
		}

		var access = await _accountAccessInviteRepository.GetAsync(userId, email, cancellationToken);
		if (access != null)
		{
			return result.Set(AccessInviteStatus.invite_exists);
		}

		var invite = new AccountAccessInvite
		{
			Id = Guid.NewGuid(),
			CreatedOn = DateTime.UtcNow,
			UserId = userId,
			AccountId = account.Id,
			Email = email.SafeTrim()!,
			IsAllowWrite = isAllowWrite,
		};

		await _accountAccessInviteRepository.AddAsync(invite, cancellationToken);

		return result.Set(_mapper.Map<AccountAccessInviteDto>(invite));
	}

	public async Task<Exec<AccountDto, GeneralExecStatus>> AccessUpdateAsync(
		Guid userId,
		Guid accountId,
		List<AccountAccessDto> accesses,
		CancellationToken cancellationToken = default)
	{
		if (accesses == null)
			throw new ArgumentNullException(nameof(accesses));

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

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

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

		foreach (var access in account.AccessRights!)
		{
			var newAccess = accesses.FirstOrDefault(x => x.UserId == access.UserId);
			if (newAccess != null && newAccess.IsAllowWrite != access.IsAllowWrite)
			{
				access.IsAllowWrite = newAccess.IsAllowWrite;
				await _accountAccessRepository.UpdateAsync(access, cancellationToken);
			}
		}

		account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);

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

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

		var account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);
		if (account == null || !account.AccessRights!.Any() || account.AccessRights!.Count() == 1)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

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

		var access = account.AccessRights!.FirstOrDefault(x => x.UserId == accessUserId);
		if (access == null)
		{
			return result.Set(GeneralExecStatus.not_found);
		}
		if (access.OwnerId == access.UserId)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		await _accountAccessRepository.DeleteAsync(access, cancellationToken);

		account = await _accountRepository.GetAsync(userId, accountId, cancellationToken);

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

	}

	public async Task<List<AccountAccessInviteDto>> InvitesGetAsync(string email, CancellationToken cancellationToken = default)
	{
		var invites = await _accountAccessInviteRepository.GetActiveAsync(email, cancellationToken);

		return _mapper.Map<List<AccountAccessInviteDto>>(invites);
	}

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

		var invite = await _accountAccessInviteRepository.GetAsync(id, cancellationToken);
		if (invite == null
			|| !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email)
			|| invite.AcceptedOn.HasValue
			|| invite.RejectedOn.HasValue
		)
		{
			return result.Set(InviteAcceptStatus.invite_not_found);
		}

		var account = await _accountRepository.GetAsync(invite.UserId, invite.AccountId, cancellationToken);
		if (account == null)
		{
			return result.Set(InviteAcceptStatus.account_not_found);
		}

		if (account.AccessRights!.Any(x => x.UserId == userId))
		{
			result.Set(InviteAcceptStatus.already_accepted);
		}
		else
		{
			await _accountAccessRepository.AddAsync(new AccountAccess
			{
				UserId = userId,
				AccountId = account.Id,
				OwnerId = invite.UserId,
				Name = name,
				IsAllowRead = true,
				IsAllowWrite = invite.IsAllowWrite,
				IsAllowManage = false,
				Type = AccountAccessTypeEnum.external,
			}, cancellationToken);
		}

		invite.AcceptedOn = DateTime.UtcNow;
		await _accountAccessInviteRepository.UpdateAsync(invite, cancellationToken);

		account = await _accountRepository.GetAsync(userId, account.Id, cancellationToken);

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

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

		var invite = await _accountAccessInviteRepository.GetAsync(id, cancellationToken);
		if (invite == null
			|| !invite.Email.EqualsIgnoreCase(_contextProvider.User.Email)
			|| invite.AcceptedOn.HasValue
			|| invite.RejectedOn.HasValue
		)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		invite.RejectedOn = DateTime.UtcNow;
		await _accountAccessInviteRepository.UpdateAsync(invite, cancellationToken);

		return result.Set(_mapper.Map<AccountAccessInviteDto>(invite));
	}
}
