package cz.muni.fi.pa165.core.common;

import cz.muni.fi.pa165.core.helpers.exceptions.EntityDeletionException;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * A generic service for domain objects.
 *
 * @param <T> the type of the domain object.
 * @author Marek Skácelík
 */
public abstract class DomainService<T extends DomainObject> {

	public static final int DEFAULT_PAGE_SIZE = 10;

	/**
	 * Returns the repository for the domain object.
	 *
	 * @return the repository.
	 */
	public abstract JpaRepository<T, String> getRepository();

	/**
	 * Creates the given entity.
	 *
	 * @param entity the entity to create.
	 * @return the created entity.
	 */
	@Transactional
	public T create(T entity) {
		entity.setCreatedAt(Instant.now());
		return getRepository().save(entity);
	}

	/**
	 * Returns all entities as a pageable list.
	 *
	 * @param pageable the pageable parameters.
	 * @return a pageable list of all entities.
	 */
	@Transactional(readOnly = true)
	public Page<T> findAllPageable(Pageable pageable) {
		return getRepository().findAll(pageable);
	}

	/**
	 * Returns all entities.
	 *
	 * @return a list of all entities.
	 */
	@Transactional(readOnly = true)
	public List<T> findAll() {
		return getRepository().findAll().stream().filter(entity -> entity.getDeletedAt() == null).toList();
	}

	/**
	 * Returns all entities as a pageable list.
	 *
	 * @param page the page number.
	 * @return a pageable list of all entities.
	 */
	@Transactional(readOnly = true)
	public Page<T> findAllPageableInt(int page) {
		return getRepository().findAll(PageRequest.of(page, DEFAULT_PAGE_SIZE))/*.filter(entity -> entity.deletedDateTime == null)*/;
	}

	/**
	 * Returns the entity with the given ID.
	 *
	 * @param id the ID of the entity to retrieve.
	 * @return the entity with the given ID.
	 * @throws EntityNotFoundException if the entity does not exist.
	 */
	@Transactional(readOnly = true)
	public T findById(String id) {
		return getRepository()
				.findById(id)
				.filter(entity -> entity.getDeletedAt() == null)
				.orElseThrow(() -> new EntityNotFoundException("Entity with '" + id + "' not found."));
	}


	/**
	 * Deletes all entities by setting their deletion datetime.
	 */
	@Transactional
	public void deleteAll() {
		List<T> entities = findAll();
		entities.stream().forEach(entity -> entity.setDeletedAt(Instant.now()));
		getRepository().saveAll(entities);
	}

	/**
	 * Deletes all entities by array of id's
	 */
	@Transactional
	public void deleteAllById(String[] ids) throws EntityDeletionException {
		for (String id : ids) {
			deleteById(id);
		}
	}

	/**
	 * Deletes the entity with the given ID by setting its deletion datetime.
	 *
	 * @param id the ID of the entity to delete.
	 * @return the deleted entity.
	 * @throws EntityNotFoundException if the entity does not exist.
	 */
	@Transactional
	public T deleteById(String id) throws EntityDeletionException {
		T entity = findById(id);
		if (entity == null)
			throw new EntityNotFoundException("Entity '" + id + "' not found.");
		entity.setDeletedAt(Instant.now());
		getRepository().save(entity);
		return entity;
	}

	/**
	 * Updates the entity with the given ID.
	 *
	 * @param entityToUpdate the updated entity.
	 * @param id             the ID of the entity to update.
	 * @return the updated entity.
	 * @throws EntityNotFoundException if the entity does not exist.
	 */
	@Transactional
	public T update(T entityToUpdate, String id) {
		T entity = findById(id);
		if (entity == null)
			throw new EntityNotFoundException("Entity '" + id + "' not found.");
		// TODO: change when ORM tool available
		entityToUpdate.setId(id);
		return getRepository().save(entityToUpdate);
	}


	/**
	 * Deletes all entities from the database without soft-delete functionality.
	 */
	@Transactional
	public void hardDeleteAll() {
		getRepository().deleteAll();
	}
}
