namespace MyOffice.Services.Item;

using AutoMapper;
using Domain;
using MyOffice.Core;
using MyOffice.Data.Models.Items;
using MyOffice.Data.Repositories.Item;
using MyOffice.Services.Account.Domain;

public class ItemService
{
	private readonly IItemCategoryRepository _itemCategoryRepository;
	private readonly IItemRepository _itemRepository;
	private readonly IItemGlobalRepository _itemGlobalRepository;
	private readonly IMapper _mapper;

	public ItemService(
		IItemCategoryRepository itemCategoryRepository,
		IItemRepository itemRepository,
		IItemGlobalRepository itemGlobalRepository,
		IMapper mapper
	)
	{
		_itemCategoryRepository = itemCategoryRepository;
		_itemRepository = itemRepository;
		_itemGlobalRepository = itemGlobalRepository;
		_mapper = mapper;
	}

	public async Task<List<ItemCategoryDto>> GetAllCategoriesAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		var result = await _itemCategoryRepository.GetAllAsync(userId, cancellationToken);
		if (result.All(x => x.Id != userId))
		{
			var category = new ItemCategory
			{
				Id = userId,
				UserId = userId,
				Name = "UnCategorized",
			};
			await _itemCategoryRepository.AddAsync(category, cancellationToken);

			result.Add((await _itemCategoryRepository.GetAsync(userId, userId, cancellationToken))!);
		}
		return _mapper.Map<List<ItemCategoryDto>>(result);
	}

	public async Task<Exec<ItemCategoryDto, GeneralExecStatus>> CategoryAddAsync(
		Guid userId,
		ItemCategoryDto input,
		CancellationToken cancellationToken = default)
	{
		if (input == null)
			throw new ArgumentNullException(nameof(input));

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

		var category = new ItemCategory
		{
			Id = Guid.NewGuid(),
			UserId = userId,
			Name = input.Name,
			IsInternal = input.IsInternal,
		};

		if (!await _itemCategoryRepository.AddAsync(category, cancellationToken))
		{
			return result.Set(GeneralExecStatus.failure);
		}

		return result.Set(_mapper.Map<ItemCategoryDto>(category));
	}

	public async Task<Exec<ItemCategoryDto, GeneralExecStatus>> CategoryUpdateAsync(
		Guid userId,
		Guid id,
		ItemCategoryDto category,
		CancellationToken cancellationToken = default)
	{
		if (category == null)
			throw new ArgumentNullException(nameof(category));

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

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

		exists.Name = category.Name;
		exists.IsInternal = category.IsInternal;

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

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

	public async Task<Exec<ItemCategoryDto, ItemCategoryRemoveResult>> CategoryRemoveAsync(
		Guid userId,
		Guid id,
		CancellationToken cancellationToken = default)
	{
		var result = new Exec<ItemCategoryDto, ItemCategoryRemoveResult>(ItemCategoryRemoveResult.success);

		var exists = await _itemCategoryRepository.GetAsync(userId, id, cancellationToken);
		if (exists == null)
		{
			return result.Set(ItemCategoryRemoveResult.not_found);
		}

		if (exists.Items.Any())
		{
			return result.Set(ItemCategoryRemoveResult.accounts_exists);
		}
		if (!await _itemCategoryRepository.RemoveAsync(exists, cancellationToken))
		{
			return result.Set(ItemCategoryRemoveResult.failure);
		}

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

	public async Task<List<ItemDto>> GetAllAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		return _mapper.Map<List<ItemDto>>(await _itemRepository.GetAllAsync(userId, cancellationToken));
	}

	public async Task<List<ItemDto>> GetByCategoryAsync(
		Guid userId,
		Guid categoryId,
		CancellationToken cancellationToken = default)
	{
		return _mapper.Map<List<ItemDto>>(await _itemRepository.GetByCategoryAsync(userId, categoryId, cancellationToken));
	}

	public async Task<List<ItemDto>> GetByUncategorizedAsync(Guid userId, CancellationToken cancellationToken = default)
	{
		var itemGlobals = await _itemGlobalRepository.GetAvailableToUserAsync(userId, cancellationToken);
		foreach (var itemGlobal in itemGlobals)
		{
			await GetOrCreateAsync(userId, itemGlobal, cancellationToken);
		}

		return await GetByCategoryAsync(userId, userId, cancellationToken);
	}

	public async Task<Exec<ItemDto, GeneralExecStatus>> UpdateAsync(
		Guid userId,
		Guid motionId,
		Guid categoryId,
		CancellationToken cancellationToken = default)
	{
		var result = new Exec<ItemDto, GeneralExecStatus>(GeneralExecStatus.success);

		var item = await _itemRepository.GetByGlobalAsync(userId, motionId, cancellationToken);
		if (item == null)
		{
			return result.Set(GeneralExecStatus.not_found);
		}

		item.CategoryId = categoryId;
		await _itemRepository.UpdateAsync(item, cancellationToken);

		return result.Set(_mapper.Map<ItemDto>(item));
	}

	public async Task<Exec<Item, ItemGetOrAddResult>> GetOrCreateAsync(
		Guid userId,
		string name,
		CancellationToken cancellationToken = default
	)
	{
		if (name == null)
			throw new ArgumentNullException(nameof(name));

		var result = new Exec<Item, ItemGetOrAddResult>(ItemGetOrAddResult.success);

		var itemGlobal = await _itemGlobalRepository.GetByNameAsync(name, cancellationToken);
		if (itemGlobal == null)
		{
			itemGlobal = new ItemGlobal
			{
				Id = Guid.NewGuid(),
				Name = name.Trim(),
			};

			if (!await _itemGlobalRepository.AddAsync(itemGlobal, cancellationToken))
			{
				return result.Set(ItemGetOrAddResult.failure);
			}

			return await GetOrCreateAsync(userId, itemGlobal, cancellationToken);
		}

		return await GetOrCreateAsync(userId, itemGlobal, cancellationToken);
	}

	private async Task<Exec<Item, ItemGetOrAddResult>> GetOrCreateAsync(
		Guid userId,
		ItemGlobal itemGlobal,
		CancellationToken cancellationToken = default
	)
	{
		if (itemGlobal == null)
			throw new ArgumentNullException(nameof(itemGlobal));

		var result = new Exec<Item, ItemGetOrAddResult>(ItemGetOrAddResult.success);

		var item = await _itemRepository.GetByGlobalAsync(userId, itemGlobal.Id, cancellationToken);
		if (item == null)
		{
			item = new Item
			{
				CategoryId = userId,
				ItemGlobalId = itemGlobal.Id,
			};
			await _itemRepository.AddAsync(item, cancellationToken);
		}

		item.ItemGlobal = itemGlobal;
		return result.Set(item);
	}

	public async Task<List<ItemDto>> FindItemsAsync(
		Guid userId,
		string term,
		int limit = 15,
		CancellationToken cancellationToken = default)
	{
		if (term == null)
			throw new ArgumentNullException(nameof(term));

		return _mapper.Map<List<ItemDto>>(await _itemRepository.FindAsync(userId, term, limit, cancellationToken));
	}

	public async Task<GeneralExecStatus> UpdateItemsCategoryAsync(
		Guid userId,
		Guid categoryId,
		List<Guid> items,
		CancellationToken cancellationToken = default)
	{
		var category = await _itemCategoryRepository.GetAsync(userId, categoryId, cancellationToken);
		if (category == null)
		{
			return GeneralExecStatus.not_found;
		}

		foreach (var item in items)
		{
			var dbItem = await _itemRepository.GetByGlobalAsync(userId, item, cancellationToken);
			if (dbItem != null)
			{
				dbItem.CategoryId = category.Id;
				await _itemRepository.UpdateAsync(dbItem, cancellationToken);
			}
		}

		return GeneralExecStatus.success;
	}
}
