namespace MyOffice.Services.Users;

using Domain;
using Core;
using Data.Models.Users;
using Data.Repositories.Users;

public class UserService
{
	private readonly IUserRepository _userRepository;
	private readonly IUserExternalRepository _userExternalRepository;
	private readonly SemaphoreSlim _addExternalLock = new(1, 1);

	public UserService(
		IUserRepository userRepository,
		IUserExternalRepository userExternalRepository
	)
	{
		_userRepository = userRepository;
		_userExternalRepository = userExternalRepository;
	}

	public async Task<User?> Get(Guid id)
	{
		return await _userRepository.GetUserAsync(id);
	}

	public async Task<List<UserExternal>> GetUserExternals(Guid userId)
	{
		return await _userExternalRepository.GetUserExternalsByUserIdAsync(userId);
	}

	public async Task<UserExternal?> GetUserExternal(Guid userId, string provider)
	{
		return await _userExternalRepository.GetByUserIdAsync(userId, provider);
	}

	public async Task<Exec<UserExternal>> RemoveUserExternal(
		Guid userId,
		string provider,
		CancellationToken cancellationToken = default)
	{
		var result = new Exec<UserExternal>();

		var userExternal = await _userExternalRepository.GetByUserIdAsync(userId, provider);
		if (userExternal == null) return result.Set(GeneralExecStatus.not_found);

		await _userExternalRepository.RemoveUserExternalAsync(userExternal, cancellationToken);

		return result.Set(userExternal);
	}

	public async Task<Exec<UserExternal, AddUserExternalStatusEnum>> AddUserExternalAsync(
		Guid userId,
		string provider,
		string externalId,
		string email,
		CancellationToken cancellationToken = default
	)
	{
		var result = new Exec<UserExternal, AddUserExternalStatusEnum>(AddUserExternalStatusEnum.success);

		var externalLogin = await _userExternalRepository.GetByUserIdAsync(userId, provider);

		if (externalLogin != null) return result.Set(externalLogin);

		await _addExternalLock.WaitAsync(cancellationToken);
		try
		{
			externalLogin = await _userExternalRepository.GetByUserIdAsync(userId, provider);
			if (externalLogin != null) return result.Set(externalLogin);

			externalLogin = await _userExternalRepository.GetByExternalIdAsync(externalId, provider);
			if (externalLogin != null) return result.Set(AddUserExternalStatusEnum.externalid_used);

			var user = await _userRepository.GetByUserUserNameAsync(email);
			if (user?.Id != userId) return result.Set(AddUserExternalStatusEnum.user_not_valid);

			externalLogin = new UserExternal
			{
				UserId = userId,
				CreatedOn = DateTime.UtcNow,
				Provider = provider.ToLower(),
				ExternalId = externalId,
				Email = email
			};
			await _userExternalRepository.AddUserExternalAsync(externalLogin, cancellationToken);
		}
		finally
		{
			_addExternalLock.Release();
		}

		return result;
	}
}
