package link;

import category.Category;
import category.CategoryId;
import category.CategoryName;
import category.CategoryRepository;
import exeptions.CategroyDoesNotExist;
import exeptions.URLIsNotReachable;
import tag.TagName;
import tag.TaggingUseCase;
import user.Username;

import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class LinkUseCase {

	private final LinkRepository linkRepository;
	private final CategoryRepository categoryRepository;
	private final TaggingUseCase taggingUseCase;
	private final RandomLinkIdGenerator linkIdGenerator;

	public LinkUseCase(LinkRepository linkRepository,
	                   CategoryRepository categoryRepository,
	                   TaggingUseCase taggingUseCase,
	                   RandomLinkIdGenerator linkIdGenerator) {
		this.linkRepository = linkRepository;
		this.categoryRepository = categoryRepository;
		this.taggingUseCase = taggingUseCase;
		this.linkIdGenerator = linkIdGenerator;
	}

	public void addLink(LinkUrl url, Set<CategoryName> categoryNames, Username creator) {
		if (!OnlineCheck.isReachable(url)) {
			throw new URLIsNotReachable("The url " + url + " does not seem online. Check if it is correct and you have internet access.");
		}
		var categoryIds = categoryNames.stream().map(categoryRepository::getIdByName).collect(Collectors.toSet());
		var tags = taggingUseCase.getTagsFor(url);
		var id = linkIdGenerator.generateId();
		var link = new Link(id, creator, url, categoryIds, tags);
		linkRepository.add(link);
	}

	public Set<LinkDto> getLinks() {
		return linkRepository.getAll().stream().map(this::convertLink).collect(Collectors.toSet());
	}

	public Set<LinkDto> getLinksForCategory(CategoryName categoryName) {
		return linkRepository
				.getByCategory(categoryRepository.getIdByName(categoryName))
				.stream()
				.map(this::convertLink)
				.collect(Collectors.toSet());
	}

	public Set<LinkDto> getLinksForTag(TagName tagName) {
		return linkRepository.getByTag(tagName).stream().map(this::convertLink).collect(Collectors.toSet());
	}

	public Set<LinkDto> getLinksForUser(Username username) {
		return linkRepository.getByUser(username).stream().map(this::convertLink).collect(Collectors.toSet());
	}

	public List<LinkDto> groupByHosts() {
		return linkRepository.groupByHosts().stream().map(this::convertLink).collect(Collectors.toList());
	}

	private LinkDto convertLink(Link link) {
		return new LinkDto(link.getCreator(), link.getUrl(), getCategoriesOf(link), link.getTags());
	}

	private Set<Category> getCategoriesOf(Link link) {
		return link.getCategoryIds().stream().map(getCategoryForId()).collect(Collectors.toSet());
	}

	private Function<CategoryId, Category> getCategoryForId() {
		return id -> categoryRepository
				.getById(id)
				.orElseThrow(() -> new CategroyDoesNotExist(
						"A Category for a certain id does not exits. You must create it first."));
	}

}
