package de.effigenix.infrastructure.production.persistence.repository;

import de.effigenix.domain.production.*;
import de.effigenix.infrastructure.production.persistence.mapper.BatchMapper;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Repository
@Profile("!no-db")
@Transactional(readOnly = true)
public class JpaBatchRepository implements BatchRepository {

    private static final Logger logger = LoggerFactory.getLogger(JpaBatchRepository.class);

    private final BatchJpaRepository jpaRepository;
    private final BatchMapper mapper;

    public JpaBatchRepository(BatchJpaRepository jpaRepository, BatchMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.mapper = mapper;
    }

    @Override
    public Result<RepositoryError, Optional<Batch>> findById(BatchId id) {
        try {
            Optional<Batch> result = jpaRepository.findById(id.value())
                    .map(mapper::toDomain);
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findById", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findAll() {
        try {
            List<Batch> result = jpaRepository.findAll().stream()
                    .map(mapper::toDomain)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findAll", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, Optional<Batch>> findByBatchNumber(BatchNumber batchNumber) {
        try {
            Optional<Batch> result = jpaRepository.findByBatchNumber(batchNumber.value())
                    .map(mapper::toDomain);
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByBatchNumber", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByStatus(BatchStatus status) {
        try {
            List<Batch> result = jpaRepository.findByStatus(status.name()).stream()
                    .map(mapper::toDomain)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByStatus", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByProductionDate(LocalDate date) {
        try {
            List<Batch> result = jpaRepository.findByProductionDate(date).stream()
                    .map(mapper::toDomain)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByProductionDate", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByRecipeIds(List<RecipeId> recipeIds) {
        try {
            List<String> ids = recipeIds.stream().map(RecipeId::value).toList();
            List<Batch> result = jpaRepository.findByRecipeIdIn(ids).stream()
                    .map(mapper::toDomain)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByRecipeIds", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findAllSummary() {
        try {
            List<Batch> result = jpaRepository.findAll().stream()
                    .map(mapper::toDomainSummary)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findAllSummary", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByStatusSummary(BatchStatus status) {
        try {
            List<Batch> result = jpaRepository.findByStatus(status.name()).stream()
                    .map(mapper::toDomainSummary)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByStatusSummary", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByProductionDateSummary(LocalDate date) {
        try {
            List<Batch> result = jpaRepository.findByProductionDate(date).stream()
                    .map(mapper::toDomainSummary)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByProductionDateSummary", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Batch>> findByRecipeIdsSummary(List<RecipeId> recipeIds) {
        try {
            List<String> ids = recipeIds.stream().map(RecipeId::value).toList();
            List<Batch> result = jpaRepository.findByRecipeIdIn(ids).stream()
                    .map(mapper::toDomainSummary)
                    .collect(Collectors.toList());
            return Result.success(result);
        } catch (Exception e) {
            logger.trace("Database error in findByRecipeIdsSummary", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    @Transactional
    public Result<RepositoryError, Void> save(Batch batch) {
        try {
            var existing = jpaRepository.findById(batch.id().value());
            if (existing.isPresent()) {
                mapper.updateEntity(existing.get(), batch);
            } else {
                jpaRepository.save(mapper.toEntity(batch));
            }
            return Result.success(null);
        } catch (ObjectOptimisticLockingFailureException e) {
            logger.warn("Optimistic locking failure for batch {}", batch.id().value());
            return Result.failure(new RepositoryError.ConcurrentModification(
                    "Batch was modified by another transaction"));
        } catch (Exception e) {
            logger.trace("Database error in save", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }
}
