package com.sonymusic.dx.dxadvsearchservice.service;

import com.sonymusic.dx.dxadvsearchservice.dto.*;
import com.sonymusic.dx.dxadvsearchservice.model.ListModel;
import com.sonymusic.dx.dxadvsearchservice.model.TrackModel;
import com.sonymusic.dx.dxadvsearchservice.searchModel.TrackSearchModel;
import com.sonymusic.dx.dxadvsearchservice.util.ConvertString;
import com.sonymusic.dx.dxadvsearchservice.util.DxSearchUtil;
import com.sonymusic.dx.persistence.gras.*;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class TrackService {

    @PersistenceContext(name = "dx-gras-persistence")
    private EntityManager em;

    @Autowired
    private ConvertString converter;

    private final Logger log = LoggerFactory.getLogger(getClass());

    public ListModel<TrackModel> advSearchTrack(TrackSearchModel tracks, int page, int size) throws ParseException {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<TrackDTO> cq1 = cb.createQuery(TrackDTO.class);
        Root<Track> root = cq1.from(Track.class);
        Join<Track, Song> trackSongJoin = root.join("song", JoinType.LEFT);
        Join<Track, Language> trackLanguageJoin = root.join("language", JoinType.LEFT);
        Join<Track, LyricsType> trackLyricsTypeJoin = root.join("lyricsType", JoinType.LEFT);
        Join<Track, Company> trackCompanyJoin = root.join("repertoireOwner", JoinType.LEFT);
        Join<Track, Company> trackMaintenanceJoin = root.join("maintenanceOwner",JoinType.LEFT);
        Join<Company,LicenseType> companyLicenseTypeJoin = trackCompanyJoin.join("licenseType",JoinType.LEFT);
        Join<Track, TrackType> trackTrackTypeJoin = root.join("trackType", JoinType.LEFT);
        Join<Track, Genre> trackGenreJoin = root.join("genre", JoinType.LEFT);
        Join<Track, TrackSubType> trackSubTypeJoin = root.join("trackSubType", JoinType.LEFT);
        Join<Song, Language> songLanguageJoin = trackSongJoin.join("language", JoinType.LEFT);
        Join<Track, DataSource> trackDataSourceJoin = root.join("dataSource", JoinType.LEFT);
        Join<Track, TrackPk> trackTrackPkJoin = root.join("trackPk", JoinType.LEFT);
        Join<Track, TrackMediaType>  trackMediaTypeJoin = root.join("trackMediaType", JoinType.LEFT);
        Join<Track, SoundCode> trackSoundCodeJoin = root.join("soundCode", JoinType.LEFT);
        Join<Track,AudioEncodingType> trackAudioEncodingTypeJoin = root.join("sourceAudioEncodingType",JoinType.LEFT);
//        Join<Track,TrackSplitDeal> trackTrackSplitDealJoin =root.join("trackSplitDeals",JoinType.LEFT);
//        Join<TrackSplitDeal,SplitDeal> trackSplitDealSplitDealJoin = trackTrackSplitDealJoin.join("splitDeal",JoinType.LEFT);
        Join<Track,Country> trackCountryJoin = root.join("copyOwnerCountry", JoinType.LEFT);
        Predicate productPredicates = buildPredicates(cb, root, tracks);
        cq1.where(productPredicates);
        cq1.select(cb.construct(TrackDTO.class,
                root.get("isrc"),
                root.get("displayTrackNumber"),
                root.get("isComplete"),
                root.get("takenFromIsrc"),
                root.get("trackMatchNameSupplemental"),
                root.get("completeUser"),
                root.get("gridNo"),
                root.get("pLine"),
                root.get("cLine"),
                root.get("raasInitiatingUser"),
                root.get("matrixNo"),
                root.get("playTime"),
                root.get("raasInitiatedDate"),
                root.get("dateSetComplete"),
                root.get("maintenanceOwner").get("hasPpbrights"),
                cb.construct(AudioEncodingTypeDTO.class,
                        trackAudioEncodingTypeJoin.get("audioEncodingKey"),
                        trackAudioEncodingTypeJoin.get("audioEncodingName")),
                cb.construct(SongLanguageDTO.class, songLanguageJoin.get("languageKey"),
                        songLanguageJoin.get("languageName")),
                cb.construct(TrackLanguageDTO.class, trackLanguageJoin.get("languageKey"),
                        trackLanguageJoin.get("languageName")),
                cb.construct(TrackLyricsDTO.class, trackLyricsTypeJoin.get("lyricsTypeKey"),
                        trackLyricsTypeJoin.get("lyricsTypeName")),
                cb.construct(TrackRepertoireOwnerDTO.class, trackCompanyJoin.get("companyKey"),
                        trackCompanyJoin.get("companyName")),
                cb.construct(TrackTypeDTO.class, trackTrackTypeJoin.get("trackTypeName"),
                        trackTrackTypeJoin.get("trackTypeKey")),
                cb.construct(MaintenanceOwnerDTO.class,
                        trackMaintenanceJoin.get("companyKey"),
                        trackMaintenanceJoin.get("companyName")),
                cb.construct(GenreDTO.class,
                        trackGenreJoin.get("genreKey"),
                        trackGenreJoin.get("genreName")),
                cb.construct(TrackSubTypeDTO.class,
                        trackSubTypeJoin.get("trackSubTypeKey"),
                        trackSubTypeJoin.get("trackSubTypeName")),
                cb.construct(DataSourceDTO.class,
                        trackDataSourceJoin.get("dataSourceId"),
                        trackDataSourceJoin.get("dataSourceName")),
                cb.construct(SongDTO.class,
                        trackSongJoin.get("songNumber"),
                        trackSongJoin.get("trackName")),
                cb.construct(TrackPkDTO.class,
                        trackTrackPkJoin.get("trackNo"),
                        trackTrackPkJoin.get("trackExt")),
                cb.construct(TrackMediumDTO.class,
                        trackMediaTypeJoin.get("mediaTypeKey"),
                        trackMediaTypeJoin.get("mediaTypeName")),
                cb.construct(LicenseTypeDTO.class,
                        companyLicenseTypeJoin.get("licenseTypeKey"),
                        companyLicenseTypeJoin.get("licenseTypeName")),
                cb.construct(ContentDTO.class,
                        trackSoundCodeJoin.get("soundCodeKey"),
                        trackSoundCodeJoin.get("soundCodeName")),
                cb.construct(CountryDTO.class,
                        trackCountryJoin.get("countryKey"),
                        trackCountryJoin.get("countryName"))
        ));

//        cq1.distinct(true);
        List<TrackDTO> trackDTOS = em.createQuery(cq1).setFirstResult((page - 1) * size).setMaxResults(size)
                .getResultList();
        List<TrackPkDTO> trackIds = trackDTOS.stream().map(TrackDTO::getTrackPk).distinct()
                .filter(Objects::nonNull).toList();
        List<String> trackNos = trackIds.stream().map(TrackPkDTO::getTrackNo).distinct()
                .filter(Objects::nonNull).toList();
        if (!trackNos.isEmpty()){
            CriteriaQuery<TrackParticipantDTO> pcq = cb.createQuery(TrackParticipantDTO.class);
            Root<TrackParticipant> pr = pcq.from(TrackParticipant.class);
            Join<TrackParticipant, Participant> part = pr.join("participant", JoinType.LEFT);
            pcq.multiselect( pr.get("trackNo").alias("trackNo"),part.get("fullName").alias("fullName"));
            pcq.where(pr.get("trackNo").in(trackNos));
            List<TrackParticipantDTO> participantRow = em.createQuery(pcq).getResultList();
            System.out.println(participantRow);
            Map<String, List<TrackParticipantDTO>> nameOfParticipant = participantRow.stream()
                    .filter(r->r.getParticipant()!=null)
                    .collect(Collectors.groupingBy(
                            TrackParticipantDTO::getTrackNo
                    ));
            for (TrackDTO dto : trackDTOS) {
                List<TrackParticipantDTO> names = nameOfParticipant.getOrDefault(dto.getTrackPk().getTrackNo(), Collections.emptyList());
                String joined = names.stream()
                        .map(TrackParticipantDTO::getParticipant).distinct().collect(Collectors.joining("; "));
                dto.setParticipant(joined);
            }
            CriteriaQuery<TrackSplitDealDTO> pq = cb.createQuery(TrackSplitDealDTO.class);
            Root<TrackSplitDeal> tsd = pq.from(TrackSplitDeal.class);
            Join<TrackSplitDeal, SplitDeal> tsdsd = tsd.join("splitDeal", JoinType.LEFT);
            pq.select(cb.construct(
                    TrackSplitDealDTO.class,
                    tsd.get("dealNo"),
                    tsd.get("trackNo"),
                    tsdsd.get("dealName")
            ));
            pq.where(tsd.get("trackNo").in(trackNos));
            List<TrackSplitDealDTO> rows = em.createQuery(pq).getResultList();
            Map<String, List<TrackSplitDealDTO>> splitDealMap = rows.stream()
                    .filter(r->r.getDealName()!=null)
                    .collect(Collectors.groupingBy(
                            TrackSplitDealDTO::getTrackNo
                    ));

            CriteriaQuery<FootageTypeDTO> ft = cb.createQuery(FootageTypeDTO.class);
            Root<Track> ftr = ft.from(Track.class);
            Join<Track,FootageType> trackFootageTypeJoin = ftr.join("footageTypes",JoinType.INNER);
            ft.select(cb.construct(
                    FootageTypeDTO.class,

                    trackFootageTypeJoin.get("footageTypeKey"),
                    trackFootageTypeJoin.get("footageTypeName"),
                    ftr.get("trackPk").get("trackNo")
            ));
            ft.where(ftr.get("trackPk").get("trackNo").in(trackNos));
            List<FootageTypeDTO> footageRows = em.createQuery(ft).getResultList();
            Map<String, List<FootageTypeDTO>> footageMap = footageRows.stream()
                    .filter(r->r.getFootageTypeName()!=null)
                    .collect(Collectors.groupingBy(
                            FootageTypeDTO::getTrackNo
                    ));

//            CriteriaQuery<ParentRepOwnerDTO> pro = cb.createQuery(ParentRepOwnerDTO.class);
//            Root<ParentRepertoireOwner> pro1 = pro.from(ParentRepertoireOwner.class);
//            Join<ParentRepertoireOwner, Company> proc = pro1.join("company", JoinType.LEFT);
//            pro.select(cb.construct(
//                    ParentRepOwnerDTO.class,
//                    proc.get("companyName")
//            ));
//            pq.where(pro1.get("companyKey").in(trackNos));


            for (TrackDTO dto : trackDTOS){
                String trackNo = dto.getTrackPk().getTrackNo();
                List<TrackSplitDealDTO> deals = splitDealMap.getOrDefault(trackNo, Collections.emptyList());
                String joined = deals.stream()
                        .map(TrackSplitDealDTO::getDealName).distinct().collect(Collectors.joining("; "));
                List<FootageTypeDTO> footageDeal = footageMap.getOrDefault(trackNo,Collections.emptyList());
                String footageJoin = footageDeal.stream()
                        .map(FootageTypeDTO::getFootageTypeName).distinct().collect(Collectors.joining("; "));
                Long dealNo = deals.stream().map(TrackSplitDealDTO::getDealNo).filter(Objects::nonNull).findFirst().orElse(null);
                if (dealNo !=null){
                    dto.setHasSplitDeal(true);
                }
                dto.setFootageName(footageJoin);
                dto.setSplitDealName(joined);
                dto.setSplitDealNo(dealNo);
            }

        }

        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
        Root<Track> countRoot = countQuery.from(Track.class);
        countQuery.select(cb.count(countRoot));
        countQuery.where(buildPredicates(cb, countRoot, tracks));
        Long totalRecords = em.createQuery(countQuery).getSingleResult();
        List<TrackModel> trackListModel = new ArrayList<>();
        TrackModel trackResponseModel = null;
        for (TrackDTO result : trackDTOS) {
            trackResponseModel = new TrackModel();
            trackResponseModel.setComplete(result.isComplete());
            if (result.getDisplayTrackNumber()==null){
                trackResponseModel.setIsrcOrTrackNo(result.getIscrNo());
            }else{
                trackResponseModel.setIsrcOrTrackNo(result.getDisplayTrackNumber());
            }

            trackResponseModel.setTakenFromIsrc(result.getTakenFromIsrc());
            trackResponseModel.setTrackTitleSupplimental(result.getTrackMatchNameSupplemental());

            //add Asset supplemental title language

            trackResponseModel.setTitleLanguage(result.getTitleLanguage().getLanguageName());
            trackResponseModel.setLanguage(result.getLanguage().getLanguageName());
            trackResponseModel.setPerformance(result.getParticipant());
            trackResponseModel.setLyricsIndicator(result.getLyricsIndicator().getLyricsTypeName());

            if (result.getPlayTime()!=null){
                long hrs = result.getPlayTime() /3600;
                long mins = (result.getPlayTime() % 3600) / 60;
                long secs = result.getPlayTime() % 3600;
                trackResponseModel.setPlayTime(String.format("%02d:%02d:%02d", hrs, mins, secs));
            }
            trackResponseModel.setRepertoireOwner(result.getRepertoireOwner().getCompanyName());

            // add business unt = parent rep owner
//            trackResponseModel.setBusinessUnit(result.getRepertoireOwner().getCompanyName());
            if(result.getCompleteDate()!=null){
                Timestamp dbValue = result.getCompleteDate();
                LocalDateTime parsedDate = dbValue.toLocalDateTime();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DxSearchUtil.DATE_FORMAT_dd_MMM_yyyy);
                String formattedDate = parsedDate.format(formatter);
                trackResponseModel.setCompleteDate(formattedDate);
            }

            trackResponseModel.setCompleteUser(result.getCompleteUser());
            trackResponseModel.setTrackMedium(result.getTrackMedium().getMediaTypeName());
            trackResponseModel.setGrId(result.getGridNo());
            trackResponseModel.setTrackType(result.getTrackType().getTrackTypeName());
            trackResponseModel.setMaintenanceOwner(result.getMaintenanceOwner().getCompanyName());
            //add composition
            //add production
            //add initiator
            if (result.getRaasInitiatedDate() != null) {
                String inputDate = result.getRaasInitiatedDate();
                SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
                Date parsedDate = format.parse(inputDate);
                SimpleDateFormat displayDate = new SimpleDateFormat(DxSearchUtil.DATE_FORMAT_dd_MMM_yyyy);
                String formattedDate = displayDate.format(parsedDate);
                trackResponseModel.setInitiatedDate(formattedDate);
            }
            trackResponseModel.setHasSplitDeal(result.isHasSplitDeal());
            trackResponseModel.setDealNo(result.getSplitDealNo());
            trackResponseModel.setSplitDealName(result.getSplitDealName());
            trackResponseModel.setOwnership(result.getLicenseTypeDTO().getLicenseTypeName());
            trackResponseModel.setGenre(result.getGenre().getGenreName());
            trackResponseModel.setContent(result.getContent().getSoundCodeName());
            trackResponseModel.setSubType(result.getTrackSubType().getTrackSubTypeName());
            trackResponseModel.setDataSource(result.getDataSource().getDataSourceName());
            //add footage name
            trackResponseModel.setFootageTypeName(result.getFootageName());
            trackResponseModel.setSongNo(result.getSong().getSongNumber());
            trackResponseModel.setpLine(result.getpLine());
            trackResponseModel.setcLine(result.getcLine());
            //add digital product number
            //add isrc verified
            trackResponseModel.setHasPpbRights(result.getMaintenanceOwner().isHasPpbrights());
            trackResponseModel.setCopyOwnerCountryName(result.getCountryName().getCountryName());
            trackResponseModel.setAudioEncounterType(result.getAudioEncodingType().getAudioEncodingName());
            trackResponseModel.setTrackNo(result.getTrackPk().getTrackNo());
            trackResponseModel.setTrackExt(result.getTrackPk().getTrackExt());
            trackResponseModel.setTrackTitle(result.getSong().getTrackName());
            trackListModel.add(trackResponseModel);
        }

        ListModel<TrackModel> listModel = new ListModel<TrackModel>();
        listModel.setItems(trackListModel);
        listModel.setTotalCount(totalRecords);
        listModel.setLimit(size);
        listModel.setTotalPageCount((int) Math.ceil((double) totalRecords / size));
        listModel.setCurPage(page);
        return listModel;
    }

    private Predicate buildPredicates(CriteriaBuilder cb, Root<Track> root, TrackSearchModel tracks) {
        List<Predicate> predicates = new ArrayList<>();
        if (tracks.getIsrcOrTrackNo() != null) {
            Predicate isrcEqual = cb.equal(root.get("isrc"), tracks.getIsrcOrTrackNo());
            Predicate displayTrackEqual = cb.like(root.get("displayTrackNumber"),
                    tracks.getIsrcOrTrackNo() + DxSearchUtil.WILD_CARD);
            predicates.add(cb.or(isrcEqual, displayTrackEqual));
        }
        if (tracks.getTakenFromIsrc() != null) {
            predicates.add(cb.equal(root.get("takenFromIsrc"), tracks.getTakenFromIsrc()));
        }
        if (tracks.getTrackTitle() != null) {
            predicates.add(cb.like(root.get("song").get("trackMatchName"),
                    converter.convertString(tracks.getTrackTitle()) + DxSearchUtil.WILD_CARD));
        }
        if (tracks.getTrackTitleSupplimental() != null) {
            predicates.add(cb.like(root.get("trackMatchNameSupplemental"),
                    converter.convertString(tracks.getTrackTitleSupplimental()) + DxSearchUtil.WILD_CARD));
        }
        if (tracks.getSongNo() != null) {
            predicates.add(cb.equal(root.get("song").get("songNumber"), tracks.getSongNo()));
        }
        if (tracks.getLanguage() != null) {
            predicates.add(cb.equal(root.get("language").get("languageKey"), tracks.getLanguage().getKey()));
        }
        if (tracks.getTitleLanguage() != null) {
            predicates.add(
                    cb.equal(root.get("song").get("language").get("languageKey"), tracks.getTitleLanguage().getKey()));
        }

        if (tracks.getLyricsInitiator() != null) {
            predicates.add(cb.equal(root.get("lyricsType").get("lyricsTypeKey"), tracks.getLyricsInitiator().getKey()));
        }
        if (tracks.getTrackType() != null) {
            predicates.add(cb.equal(root.get("trackType").get("trackTypeKey"), tracks.getTrackType().getKey()));
        }
        if (tracks.getTrackSubType()!= null) {
            predicates.add(cb.equal(root.get("trackSubType").get("trackSubTypeKey"), tracks.getTrackSubType().getKey()));
        }
        if (tracks.getTrackMedium()!= null) {
            predicates.add(cb.equal(root.get("trackMediaType").get("mediaTypeKey"), tracks.getTrackSubType().getKey()));
        }
        if (tracks.getMaintenanceOwner()!= null) {
            predicates.add(cb.equal(root.get("maintenanceOwner").get("companyKey"), tracks.getMaintenanceOwner().getKey()));
        }
        if (tracks.getInitiator()!= null) {
            predicates.add(cb.equal(root.get("raasInitiatingCompany").get("companyKey"), tracks.getInitiator().getKey()));
        }
        if (tracks.getInitiatedBy() != null) {
            predicates.add(cb.like(cb.lower(root.get("raasInitiatingUser")),tracks.getInitiatedBy().toLowerCase() + DxSearchUtil.WILD_CARD));
        }
        DateTimeFormatter inputFormat = DateTimeFormatter.ofPattern("d MMM yyyy");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        if (tracks.getInitiatedBetweenFrom() != null && tracks.getInitiatedBetweenTo() != null) {
            LocalDate parsedFromDate = LocalDate.parse(tracks.getInitiatedBetweenFrom(), inputFormat);
            LocalDate parsedToDate = LocalDate.parse(tracks.getInitiatedBetweenTo(), inputFormat);
            String formattedFromDate = parsedFromDate.format(formatter);
            String formattedToDate = parsedToDate.format(formatter);
            predicates.add(cb.between(root.get("raasInitiatedDate"), formattedFromDate, formattedToDate));
        } else if (tracks.getInitiatedBetweenFrom() != null) {
            LocalDate fromDate = LocalDate.parse(tracks.getInitiatedBetweenFrom(), inputFormat);
            String formattedFromDate = fromDate.format(formatter);
            predicates.add(cb.greaterThanOrEqualTo(root.get("raasInitiatedDate"), formattedFromDate));

        } else if (tracks.getInitiatedBetweenTo() != null) {
            LocalDate toDate = LocalDate.parse(tracks.getInitiatedBetweenTo(), inputFormat);
            String formattedToDate = toDate.format(formatter);
            predicates.add(cb.lessThanOrEqualTo(root.get("raasInitiatedDate"), formattedToDate));

        }
        if (tracks.getRepertoireOwner()!= null) {
            predicates.add(cb.equal(root.get("repertoireOwner").get("companyKey"), tracks.getRepertoireOwner().getKey()));
        }
        if (tracks.getGenre()!= null) {
            predicates.add(cb.equal(root.get("genre").get("genreKey"), tracks.getGenre().getKey()));
        }
        if (tracks.getContent()!= null) {
            predicates.add(cb.equal(root.get("soundCode").get("soundCodeKey"), tracks.getContent().getKey()));
        }
        if (tracks.getComplete()!=null){
            if (tracks.getComplete().getName().equals(DxSearchUtil.YES)){
                predicates.add(cb.equal(root.get("isComplete"), true));
            }else if (tracks.getComplete().getName().equals(DxSearchUtil.NO)){
                predicates.add(cb.equal(root.get("isComplete"), false));
            }
        }
        if (tracks.getCompleteBetweenFrom() != null && tracks.getCompleteBetweenTo() != null) {
            LocalDate fromDateTime = LocalDate.parse(tracks.getCompleteBetweenFrom(), inputFormat);
            LocalDate toDateTime = LocalDate.parse(tracks.getCompleteBetweenTo(), inputFormat);
            Timestamp fromTs = Timestamp.valueOf(fromDateTime.atStartOfDay());
            Timestamp toTs = Timestamp.valueOf(toDateTime.atTime(23,59,59));
            predicates.add(cb.between(root.get("dateSetComplete"), fromTs, toTs));

        } else if (tracks.getCompleteBetweenFrom() != null) {
            LocalDate fromDateTime = LocalDate.parse(tracks.getCompleteBetweenFrom(), inputFormat);
            Timestamp fromTs =Timestamp.valueOf(fromDateTime.atStartOfDay());
            predicates.add(cb.greaterThanOrEqualTo(root.get("dateSetComplete"), fromTs));
        } else if (tracks.getCompleteBetweenTo() != null) {
            LocalDate toDateTime = LocalDate.parse(tracks.getCompleteBetweenTo(), inputFormat);
            Timestamp toTs =Timestamp.valueOf(toDateTime.atTime(23,59,59));
            predicates.add(cb.lessThanOrEqualTo(root.get("dateSetComplete"),toTs));
        }
        if (tracks.getMatrixNo()!= null) {
            predicates.add(cb.equal(root.get("matrixNo"), tracks.getMatrixNo()));
        }
        if (tracks.getGridNo()!= null) {
            predicates.add(cb.equal(root.get("gridNo"), tracks.getGridNo()));
        }
        if (tracks.getDataSource()!= null) {
            predicates.add(cb.equal(root.get("dataSource").get("dataSourceId"), tracks.getDataSource().getKey()));
        }
        if (tracks.getOriginalPerformer()!= null) {
            predicates.add(cb.equal(root.get("trackParticipants").get("participant").get("participantNo"), tracks.getOriginalPerformer().getKey()));
        }
        if (tracks.getFootageType()!= null) {
            predicates.add(cb.equal(root.get("footageTypes").get("footageTypeKey"), tracks.getFootageType().getKey()));
        }
        if (tracks.isLive()){
            predicates.add(cb.equal(root.get("isLiveRecording"),tracks.isLive()));
        }

        return cb.and(predicates.toArray(new Predicate[0]));
    }
}
