package com.codebyte.api.item.service;

import com.codebyte.api.common.web.PagedResponse;
import com.codebyte.api.item.api.dto.CreateItemRequest;
import com.codebyte.api.item.api.dto.ItemResponse;
import com.codebyte.api.item.api.dto.PatchItemRequest;
import com.codebyte.api.item.api.dto.UpdateItemRequest;
import com.codebyte.api.item.domain.Item;
import com.codebyte.api.item.mapper.ItemMapper;
import com.codebyte.api.item.repository.ItemRepository;
import java.util.UUID;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/** Default {@link ItemService} implementation. Transactions live here, never in the controller. */
@Service
@Transactional
public class ItemServiceImpl implements ItemService {

    private final ItemRepository repository;
    private final ItemMapper mapper;

    public ItemServiceImpl(ItemRepository repository, ItemMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    @Override
    public ItemResponse create(CreateItemRequest request) {
        if (repository.existsByNameIgnoreCase(request.name())) {
            throw new DuplicateItemNameException(request.name());
        }
        Item saved = repository.saveAndFlush(mapper.toEntity(request));
        return mapper.toResponse(saved);
    }

    @Override
    @Transactional(readOnly = true)
    public ItemResponse get(UUID id) {
        return mapper.toResponse(findOrThrow(id));
    }

    @Override
    @Transactional(readOnly = true)
    public PagedResponse<ItemResponse> list(String query, Pageable pageable) {
        Page<Item> page;
        if (query == null || query.isBlank()) {
            page = repository.findAll(pageable);
        } else {
            String term = query.trim();
            page =
                    repository.findByNameContainingIgnoreCaseOrDescriptionContainingIgnoreCase(
                            term, term, pageable);
        }
        return PagedResponse.from(page.map(mapper::toResponse));
    }

    @Override
    public ItemResponse replace(UUID id, UpdateItemRequest request) {
        Item item = findOrThrow(id);
        if (item.getVersion() != request.version()) {
            throw new ItemVersionConflictException(id);
        }
        if (!item.getName().equalsIgnoreCase(request.name())
                && repository.existsByNameIgnoreCase(request.name())) {
            throw new DuplicateItemNameException(request.name());
        }
        mapper.updateEntity(request, item);
        return mapper.toResponse(repository.saveAndFlush(item));
    }

    @Override
    public ItemResponse patch(UUID id, PatchItemRequest request) {
        Item item = findOrThrow(id);
        if (request.name().isPresent()) {
            String newName = request.name().get();
            if (!item.getName().equalsIgnoreCase(newName)
                    && repository.existsByNameIgnoreCase(newName)) {
                throw new DuplicateItemNameException(newName);
            }
            item.setName(newName);
        }
        if (request.description().isPresent()) {
            item.setDescription(request.description().get());
        }
        return mapper.toResponse(repository.saveAndFlush(item));
    }

    @Override
    public void delete(UUID id) {
        // Idempotent: a missing item is a no-op, not a 404.
        repository.findById(id).ifPresent(repository::delete);
    }

    private Item findOrThrow(UUID id) {
        return repository.findById(id).orElseThrow(() -> new ItemNotFoundException(id));
    }
}
