content: package com.example.auction.service;
  
  import com.example.auction.dto.AuctionCreateDTO;
  import com.example.auction.dto.BidCreateDTO;
  import com.example.auction.exception.AuctionException;
  import com.example.auction.exception.AuctionNotFoundException;
  import com.example.auction.model.Auction;
  import com.example.auction.model.AuctionStatus;
  import com.example.auction.model.Bid;
  import com.example.auction.repository.AuctionRepository;
  import com.example.auction.repository.BidRepository;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.data.domain.PageRequest;
  import org.springframework.data.domain.Pageable;
  import org.springframework.stereotype.Service;
  import org.springframework.transaction.annotation.Transactional;
  
  import java.math.BigDecimal;
  import java.time.LocalDateTime;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Optional;
  
  @Service
  @Transactional
  public class AuctionService {
      private static final Logger logger = LoggerFactory.getLogger(AuctionService.class);
      
      @Autowired
      private AuctionRepository auctionRepository;
      
      @Autowired
      private BidRepository bidRepository;
      
      public Auction createAuction(AuctionCreateDTO dto) {
          // Check if there's already an active auction
          if (auctionRepository.existsByStatus(AuctionStatus.OPEN)) {
              throw new AuctionException("Cannot create auction: An active auction already exists");
          }
          
          // Strong typing ensures all fields are validated at compile time
          Auction auction = new Auction();
          auction.setTitle(dto.getTitle());
          auction.setDescription(dto.getDescription());
          auction.setStartingPrice(dto.getStartingPrice());
          auction.setCurrentPrice(dto.getStartingPrice());
          auction.setMinimumIncrement(dto.getMinimumIncrement());
          auction.setStatus(AuctionStatus.OPEN);
          auction.setCreatedAt(LocalDateTime.now());
          auction.setEndsAt(LocalDateTime.now().plusMinutes(dto.getDurationMinutes()));
          
          return auctionRepository.save(auction);
      }
      
      public List<Auction> getAuctions(String status, String sort) {
          // Auto-close expired auctions
          autoCloseExpiredAuctions();
          
          if (status != null) {
              try {
                  AuctionStatus auctionStatus = AuctionStatus.valueOf(status);
                  if (sort != null && sort.equals("ending_soon") && auctionStatus == AuctionStatus.OPEN) {
                      return auctionRepository.findByStatusOrderByEndsAtAsc(auctionStatus);
                  }
                  return auctionRepository.findByStatus(auctionStatus);
              } catch (IllegalArgumentException e) {
                  // Java's strong typing catches invalid enum values
                  throw new AuctionException("Invalid status: " + status);
              }
          }
          
          if ("newest".equals(sort)) {
              return auctionRepository.findAllByOrderByCreatedAtDesc();
          } else if ("ending_soon".equals(sort)) {
              return auctionRepository.findByStatusOrderByEndsAtAsc(AuctionStatus.OPEN);
          }
          
          return auctionRepository.findAllByOrderByCreatedAtDesc();
      }
      
      public Auction getAuction(String auctionId) {
          autoCloseExpiredAuctions();
          return auctionRepository.findById(auctionId)
              .orElseThrow(() -> new AuctionNotFoundException(auctionId));
      }
      
      public Bid placeBid(String auctionId, BidCreateDTO dto) {
          Auction auction = auctionRepository.findById(auctionId)
              .orElseThrow(() -> new AuctionNotFoundException(auctionId));
          
          Bid bid = new Bid();
          bid.setAuctionId(auctionId);
          bid.setBidderId(dto.getBidderId());
          bid.setBidderName(dto.getBidderName());
          bid.setAmount(dto.getAmount());  // BigDecimal ensures precision
          bid.setTimestamp(LocalDateTime.now());
          
          List<String> validationErrors = new ArrayList<>();
          
          // Strong type checking prevents runtime type errors
          if (auction.getStatus() != AuctionStatus.OPEN) {
              validationErrors.add("Auction is closed");
          }
          
          if (LocalDateTime.now().isAfter(auction.getEndsAt())) {
              validationErrors.add("Auction has ended");
              auction.setStatus(AuctionStatus.CLOSED);
          }
          
          // BigDecimal arithmetic prevents floating point errors
          BigDecimal requiredAmount = auction.getCurrentPrice().add(auction.getMinimumIncrement());
          if (dto.getAmount().compareTo(requiredAmount) < 0) {
              validationErrors.add(String.format("Bid must be at least $%.2f", requiredAmount));
          }
          
          if (!validationErrors.isEmpty()) {
              bid.setIsValid(false);
              bid.setValidationError(String.join("; ", validationErrors));
              bidRepository.save(bid);
              throw new AuctionException(bid.getValidationError());
          }
          
          // Valid bid - update auction
          bid.setIsValid(true);
          auction.setCurrentPrice(dto.getAmount());
          auction.setHighestBidderId(dto.getBidderId());
          auction.setHighestBidderName(dto.getBidderName());
          
          auctionRepository.save(auction);
          return bidRepository.save(bid);
      }
      
      public List<Bid> getBids(String auctionId, int limit) {
          // Verify auction exists
          if (!auctionRepository.existsById(auctionId)) {
              throw new AuctionNotFoundException(auctionId);
          }
          
          Pageable pageable = PageRequest.of(0, limit);
          return bidRepository.findByAuctionIdOrderByTimestampDesc(auctionId, pageable);
      }
      
      public Auction closeAuction(String auctionId) {
          Auction auction = auctionRepository.findById(auctionId)
              .orElseThrow(() -> new AuctionNotFoundException(auctionId));
          
          if (auction.getStatus() != AuctionStatus.OPEN) {
              throw new AuctionException("Auction is already closed");
          }
          
          auction.setStatus(AuctionStatus.CLOSED);
          return auctionRepository.save(auction);
      }
      
      @Transactional
      public void resetDatabase() {
          bidRepository.deleteAll();
          auctionRepository.deleteAll();
      }
      
      private void autoCloseExpiredAuctions() {
          List<Auction> expiredAuctions = auctionRepository.findExpiredAuctions(LocalDateTime.now());
          for (Auction auction : expiredAuctions) {
              auction.setStatus(AuctionStatus.CLOSED);
              auctionRepository.save(auction);
          }
      }
  }