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

import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.entity.CustomerEntity;
import de.effigenix.infrastructure.masterdata.persistence.entity.FrameContractEntity;
import de.effigenix.infrastructure.masterdata.persistence.mapper.CustomerMapper;
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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

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

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

    private final CustomerJpaRepository jpaRepository;
    private final FrameContractJpaRepository frameContractJpaRepository;
    private final CustomerMapper mapper;

    public JpaCustomerRepository(CustomerJpaRepository jpaRepository,
                                 FrameContractJpaRepository frameContractJpaRepository,
                                 CustomerMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.frameContractJpaRepository = frameContractJpaRepository;
        this.mapper = mapper;
    }

    @Override
    public Result<RepositoryError, Optional<Customer>> findById(CustomerId id) {
        try {
            var entityOpt = jpaRepository.findById(id.value());
            if (entityOpt.isEmpty()) {
                return Result.success(Optional.empty());
            }
            var fcEntity = frameContractJpaRepository.findByCustomerId(id.value()).orElse(null);
            return Result.success(Optional.of(mapper.toDomain(entityOpt.get(), fcEntity)));
        } catch (Exception e) {
            logger.trace("Database error in findById", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Customer>> findAll() {
        try {
            return Result.success(mapWithFrameContracts(jpaRepository.findAll()));
        } catch (Exception e) {
            logger.trace("Database error in findAll", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Customer>> findByType(CustomerType type) {
        try {
            return Result.success(mapWithFrameContracts(jpaRepository.findByType(type.name())));
        } catch (Exception e) {
            logger.trace("Database error in findByType", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, List<Customer>> findByStatus(CustomerStatus status) {
        try {
            return Result.success(mapWithFrameContracts(jpaRepository.findByStatus(status.name())));
        } catch (Exception e) {
            logger.trace("Database error in findByStatus", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    private List<Customer> mapWithFrameContracts(List<CustomerEntity> entities) {
        List<String> customerIds = entities.stream()
                .map(CustomerEntity::getId)
                .toList();
        Map<String, FrameContractEntity> fcMap = frameContractJpaRepository.findByCustomerIdIn(customerIds).stream()
                .collect(Collectors.toMap(FrameContractEntity::getCustomerId, Function.identity()));
        return entities.stream()
                .map(entity -> mapper.toDomain(entity, fcMap.get(entity.getId())))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional
    public Result<RepositoryError, Void> save(Customer customer) {
        try {
            jpaRepository.save(mapper.toEntity(customer));

            // Handle FrameContract separately
            frameContractJpaRepository.deleteByCustomerId(customer.id().value());
            if (customer.frameContract() != null) {
                var fcEntity = mapper.toFrameContractEntity(customer.frameContract(), customer.id().value());
                frameContractJpaRepository.save(fcEntity);
            }

            return Result.success(null);
        } catch (Exception e) {
            logger.trace("Database error in save", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    @Transactional
    public Result<RepositoryError, Void> delete(Customer customer) {
        try {
            frameContractJpaRepository.deleteByCustomerId(customer.id().value());
            jpaRepository.deleteById(customer.id().value());
            return Result.success(null);
        } catch (Exception e) {
            logger.trace("Database error in delete", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }

    @Override
    public Result<RepositoryError, Boolean> existsByName(CustomerName name) {
        try {
            return Result.success(jpaRepository.existsByName(name.value()));
        } catch (Exception e) {
            logger.trace("Database error in existsByName", e);
            return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
        }
    }
}
