package com.sonymusic.dx.dxadvsearchservice.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sonymusic.dx.dxadvsearchservice.dto.ParticipantSplitDealDTO;
import com.sonymusic.dx.dxadvsearchservice.dto.RepertoireOwnerDTO;
import com.sonymusic.dx.dxadvsearchservice.model.ListModel;
import com.sonymusic.dx.dxadvsearchservice.model.ParticipantModel;
import com.sonymusic.dx.dxadvsearchservice.searchModel.ParticipantSearchModel;
import com.sonymusic.dx.persistence.gras.Company;
import com.sonymusic.dx.persistence.gras.Participant;
import com.sonymusic.dx.persistence.gras.ParticipantSplitDeal;
import com.sonymusic.dx.persistence.gras.ParticipantSubType;
import com.sonymusic.dx.persistence.gras.RepertoireOwner;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;


@Service
public class ParticipantService {

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

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

    @Transactional
    public ListModel<ParticipantModel> advSearchParticipant(ParticipantSearchModel participant, int page, int size) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Participant> cq = cb.createQuery(Participant.class);
        Root<Participant> root = cq.from(Participant.class);
        root.fetch("participantType", JoinType.LEFT);
        root.fetch("dataSource", JoinType.LEFT);
        root.fetch("subTypes", JoinType.LEFT);
        root.fetch("initiatedByCompany",JoinType.LEFT);
        Subquery<String> subquery = cq.subquery(String.class);
        Root<RepertoireOwner> repertoireOwnerRoot = subquery.from(RepertoireOwner.class);
        subquery.select(repertoireOwnerRoot.get("repOwnerKey")).
                where(cb.equal(repertoireOwnerRoot.get("repOwnerKey"),
                        root.get("repOwnerKey")));
        cq.distinct(true);
        List<Predicate> predicates = buildPredicates(cb, root, participant);
        cq.where(predicates.toArray(new Predicate[0]));
        cq.orderBy(cb.asc(root.get("fullMatchName")));
        TypedQuery<Participant> query = em.createQuery(cq);
        int totalElements = query.getResultList().size();
        query.setFirstResult((page - 1) * size);
        query.setMaxResults(size);
        List<Participant> participants = query.getResultList();

        Set<String> repOwnerKeys = participants.stream()
                .map(Participant::getRepOwnerKey)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<String, RepertoireOwnerDTO> repertoireOwnerMap = new HashMap<>();

        if (!repOwnerKeys.isEmpty()) {
            CriteriaQuery<RepertoireOwnerDTO> repCQ = cb.createQuery(RepertoireOwnerDTO.class);
            Root<RepertoireOwner> repRoot = repCQ.from(RepertoireOwner.class);
            Join<RepertoireOwner, Company> companyJoin = repRoot.join("company", JoinType.LEFT);
            repCQ.select(cb.construct(
                    RepertoireOwnerDTO.class,
                    repRoot.get("repOwnerKey"),
                    companyJoin.get("companyName")
            )).where(repRoot.get("repOwnerKey").in(repOwnerKeys));
            List<RepertoireOwnerDTO> dtoList = em.createQuery(repCQ).getResultList();
            for (RepertoireOwnerDTO ro : dtoList) {
                repertoireOwnerMap.put(ro.getRepOwnerKey(), ro);
            }
        }

        Set<Long> participantNos = participants.stream()
                .map(Participant::getParticipantNo)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<Long, Long> participantDealMap = new HashMap<>();

        if (!participantNos.isEmpty()) {
            CriteriaQuery<ParticipantSplitDealDTO> dealCQ = cb.createQuery(ParticipantSplitDealDTO.class);
            Root<ParticipantSplitDeal> dealRoot = dealCQ.from(ParticipantSplitDeal.class);
            dealCQ.select(cb.construct(
                    ParticipantSplitDealDTO.class,
                    dealRoot.get("participantNo"),
                    dealRoot.get("dealNo")
            )).where(dealRoot.get("participantNo").in(participantNos));
            List<ParticipantSplitDealDTO> dealDTOList = em.createQuery(dealCQ).getResultList();
            for (ParticipantSplitDealDTO dto : dealDTOList) {
                participantDealMap.put(dto.getParticipantNo(), dto.getDealNo());
            }

        }

        List<ParticipantModel> participantsList = new ArrayList<>();
        ParticipantModel participantResponseModel = null;
        for (Participant result : participants) {
            participantResponseModel = new ParticipantModel();
            participantResponseModel.setFullName(result.getFullName());
            participantResponseModel.setIndexName(result.getIndexName());
            participantResponseModel.setParticipantNumber(result.getParticipantNo());
            participantResponseModel.setPreferred(result.isPreferredParticipant());
            if (result.getDataSource() != null) {
                participantResponseModel.setDataSource(result.getDataSource().getDataSourceName());
            }
            if (result.getParticipantType() != null) {
                participantResponseModel.setType(result.getParticipantType().getParticipantTypeName());
            }
            if (result.getSubTypes() != null && !result.getSubTypes().isEmpty()) {
                String subTypeNames = result.getSubTypes().stream()
                        .map(ParticipantSubType::getParticipantSubTypeName)
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(";"));
                participantResponseModel.setSubType(subTypeNames);
            }
            if (result.getRepOwnerKey() != null) {
                RepertoireOwnerDTO repOwner = repertoireOwnerMap.get(result.getRepOwnerKey());
                if (repOwner != null) {
                    participantResponseModel.setRepertoireOwner(repOwner.getCompanyName());
                }
            }
            Long count = participantDealMap.get(result.getParticipantNo());
            if (count != null && count > 0) {
                participantResponseModel.setHasSplitDeal(true);
            } else {
                participantResponseModel.setHasSplitDeal(false);
            }
            if (result.getMainArtistCount() != null && result.getMainArtistCount() > 0) {
                participantResponseModel.setMainArtist(true);
            } else {
                participantResponseModel.setMainArtist(false);
            }

            participantResponseModel.setMostFrequentTask(result.getMostFrequestTasks());
            participantResponseModel.setInitiatedDate(result.getFormattedDateInitiated());
            participantResponseModel.setInitiator(result.getInitiatedByCompany().getCompanyName());
            participantsList.add(participantResponseModel);

        }

        ListModel<ParticipantModel> listModel = new ListModel<ParticipantModel>();
        listModel.setItems(participantsList);
        listModel.setTotalCount(totalElements);
        listModel.setLimit(size);
        listModel.setTotalPageCount((int) Math.ceil((double) totalElements / size));
        listModel.setCurPage(page);
        return listModel;

    }

    private List<Predicate> buildPredicates(CriteriaBuilder cb,
                                            Root<Participant> root,
                                            ParticipantSearchModel participant
    ) {
        List<Predicate> predicates = new ArrayList<>();

        if (participant.getParticipantName()!=null){
            predicates.add(cb.like(cb.lower(root.get("fullMatchName")), participant.getParticipantName().toLowerCase() +"%"));
        }
        if (participant.getParticipantNumber()!=null){
            predicates.add(cb.equal(root.get("participantNo"),participant.getParticipantNumber()));
        }
        if (participant.getType()!=null){
            predicates.add(cb.equal(root.get("participantType").get("participantTypeId"),participant.getType().getKey()));
        }
        if (participant.getSubType()!=null){
            predicates.add(cb.equal(root.get("subTypes").get("participantSubTypeId"),participant.getSubType().getKey()));

        }
        if (participant.getDataSource()!=null){
            predicates.add(cb.equal(root.get("dataSource").get("dataSourceId"),participant.getDataSource().getKey()));
        }
        if (participant.isMainArtist()){
            predicates.add(cb.greaterThan(root.get("mainArtistCount"),0L));
        }
        if (participant.isPreferredParticipant()){
            predicates.add(cb.isTrue(root.get("isPreferredParticipant")));
        }
        if (participant.getRepertoireOwner()!=null ){
            predicates.add(cb.equal(root.get("repOwnerKey"),participant.getRepertoireOwner().getKey()));
        }

        return predicates;
    }

}