package com.saas.shared.service;

import com.saas.shared.exception.ResourceNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Abstract base implementation of CRUD service
 * Provides default implementations for all CRUD operations
 * Concrete services can override methods for custom behavior
 * 
 * @param <T>         Entity type
 * @param <ID>        ID type (usually Long)
 * @param <CreateReq> Create request DTO
 * @param <UpdateReq> Update request DTO
 * @param <Response>  Response DTO
 */
@Slf4j
public abstract class AbstractCrudService<T, ID, CreateReq, UpdateReq, Response>
        implements BaseCrudService<T, ID, CreateReq, UpdateReq, Response> {

    /**
     * Get the JPA repository for this entity
     */
    protected abstract JpaRepository<T, ID> getRepository();

    /**
     * Convert entity to response DTO
     */
    protected abstract Response toResponse(T entity);

    /**
     * Convert create request to entity
     */
    protected abstract T toEntity(CreateReq request);

    /**
     * Update entity from update request
     */
    protected abstract void updateEntity(T entity, UpdateReq request);

    /**
     * Get entity name for error messages
     */
    protected abstract String getEntityName();

    /**
     * Hook called before entity creation
     * Override to add custom validation or logic
     */
    protected void beforeCreate(CreateReq request) {
        // Default: no-op
    }

    /**
     * Hook called after entity creation
     * Override to add custom logic (e.g., send notifications)
     */
    protected void afterCreate(T entity) {
        // Default: no-op
    }

    /**
     * Hook called before entity update
     */
    protected void beforeUpdate(ID id, UpdateReq request) {
        // Default: no-op
    }

    /**
     * Hook called after entity update
     */
    protected void afterUpdate(T entity) {
        // Default: no-op
    }

    /**
     * Hook called before entity deletion
     */
    protected void beforeDelete(ID id) {
        // Default: no-op
    }

    /**
     * Hook called after entity deletion
     */
    protected void afterDelete(ID id) {
        // Default: no-op
    }

    @Override
    public Response getById(ID id) {
        log.debug("Fetching {} with ID: {}", getEntityName(), id);
        T entity = getRepository().findById(id)
                .orElseThrow(() -> new ResourceNotFoundException(
                        getEntityName() + " not found", id));
        return toResponse(entity);
    }

    @Override
    public List<Response> getAll() {
        log.debug("Fetching all {}", getEntityName());
        return getRepository().findAll().stream()
                .map(this::toResponse)
                .collect(Collectors.toList());
    }

    @Override
    public Response create(CreateReq request) {
        log.info("Creating new {}", getEntityName());

        beforeCreate(request);

        T entity = toEntity(request);
        T saved = getRepository().save(entity);

        afterCreate(saved);

        log.info("{} created successfully with ID: {}", getEntityName(), getEntityId(saved));
        return toResponse(saved);
    }

    @Override
    public Response update(ID id, UpdateReq request) {
        log.info("Updating {} with ID: {}", getEntityName(), id);

        beforeUpdate(id, request);

        T entity = getRepository().findById(id)
                .orElseThrow(() -> new ResourceNotFoundException(
                        getEntityName() + " not found", id));

        updateEntity(entity, request);
        T updated = getRepository().save(entity);

        afterUpdate(updated);

        log.info("{} updated successfully: {}", getEntityName(), id);
        return toResponse(updated);
    }

    @Override
    public void delete(ID id) {
        log.info("Deleting {} with ID: {}", getEntityName(), id);

        beforeDelete(id);

        if (!getRepository().existsById(id)) {
            throw new ResourceNotFoundException(
                    getEntityName() + " not found", id);
        }

        getRepository().deleteById(id);

        afterDelete(id);

        log.info("{} deleted successfully: {}", getEntityName(), id);
    }

    /**
     * Helper to get entity ID for logging
     * Override if entity doesn't have getId() method
     */
    protected Object getEntityId(T entity) {
        try {
            return entity.getClass().getMethod("getId").invoke(entity);
        } catch (Exception e) {
            return "unknown";
        }
    }
}
