package io.delphiplatform.api.v3.rdb.service.ads;

import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.CommonTableExpression;
import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.SelectQuery;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.SelectQueryHelper;
import io.delphiplatform.api.v3.constant.ColumnNameConstants;
import io.delphiplatform.api.v3.decibelrdb.entity.DecibelProjectEntity;
import io.delphiplatform.api.v3.decibelrdb.repository.DecibelProjectEntityRepository;
import io.delphiplatform.api.v3.model.ads.DecibelProjectSimple;
import io.delphiplatform.api.v3.rdb.service.EntityService;
import io.delphiplatform.api.v3.rdb.service.specification.SpecificationProvider;

import static io.delphiplatform.api.v3.constant.DecibelTableNames.DECIBEL_LABELS;
import static io.delphiplatform.api.v3.constant.DecibelTableNames.PROJECTS;
import static io.delphiplatform.api.v3.constant.DecibelTableNames.USER_LABELS;
import static io.delphiplatform.api.v3.constant.DecibelTableNames.USER_PROJECTS;
import static io.delphiplatform.api.v3.constant.ExpressionConstants.PROJECT_EXPRESSION_NAME;

@Service
public class DecibelProjectService extends EntityService<DecibelProjectEntity> {

    private final DecibelProjectEntityRepository repository;
    private final SpecificationProvider<DecibelProjectEntity> specificationProvider;

    public DecibelProjectService(
        DecibelProjectEntityRepository repository,
        SpecificationProvider<DecibelProjectEntity> specificationProvider) {
        super(repository);
        this.repository = repository;
        this.specificationProvider = specificationProvider;
    }

    @Transactional(readOnly = true)
    public List<DecibelProjectSimple> findAllById(Collection<String> projectIds) {
        return repository.findAllById(projectIds).stream()
            .map(DecibelProjectSimple::from)
            .collect(Collectors.toList());
    }

    @Transactional(readOnly = true)
    public Set<String> findProjects(String artistId, Set<String> projectIds) {
        if (CollectionUtils.isEmpty(artistId) && CollectionUtils.isEmpty(projectIds)) {
            return Collections.emptySet();
        }
        if (CollectionUtils.isNotEmpty(projectIds)) {
            List<DecibelProjectSimple> existing = findAllById(projectIds);
            return CollectionUtils.isEmpty(existing) ? Collections.emptySet()
                : existing.stream()
                    .map(DecibelProjectSimple::getProjectId)
                    .collect(Collectors.toSet());
        }
        Specification<DecibelProjectEntity> spec = and(List.of(
            specificationProvider.singleValueSpec(List.of("projectArtistId", "artist", "artistId"),
                artistId, List.of("artists")),
            specificationProvider.singleValueSpec("isConfidential", false)
        ));

        return getResult(spec, defaultPageable).stream()
            .map(DecibelProjectEntity::getId)
            .collect(Collectors.toSet());
    }

    @Transactional(readOnly = true)
    public DecibelProjectSimple findOne(String projectId) {
        return repository.findById(projectId)
            .map(DecibelProjectSimple::from)
            .orElseThrow(NOT_FOUND);
    }

    public CommonTableExpression findProjectsIdsExpression(Integer userId, Set<Integer> labelIds) {
        if (userId == null) {
            return null;
        }

        SelectQueryHelper helper = SelectQueryHelper.forDecibelSchema()
            .fromTable(PROJECTS).addSelectColumns(ColumnNameConstants.ID)
            .joinOn(ColumnNameConstants.ID).toTable(USER_PROJECTS, ColumnNameConstants.PROJECT_ID)
            .joinOn(ColumnNameConstants.DECIBEL_LABEL_ID).toTable(DECIBEL_LABELS, ColumnNameConstants.ID)
            .joinOn(ColumnNameConstants.ID).fromTable(DECIBEL_LABELS)
            .toTable(USER_LABELS, ColumnNameConstants.DECIBEL_LABEL_ID)
            .done();

        Condition condition = ComboCondition.and(
            BinaryCondition.equalTo(helper.getDbColumnForTable(PROJECTS, ColumnNameConstants.IS_CONFIDENTIAL),
                Boolean.FALSE.toString()),
            ComboCondition.or(
                BinaryCondition.equalTo(helper.getDbColumnForTable(USER_LABELS, ColumnNameConstants.USER_ID), userId),
                BinaryCondition.equalTo(helper.getDbColumnForTable(USER_PROJECTS, ColumnNameConstants.USER_ID), userId)
            )
        );

        SelectQuery query = helper.buildQuery()
            .setIsDistinct(true)
            .addCondition(condition);

        return new CommonTableExpression(PROJECT_EXPRESSION_NAME)
            .setQuery(query.validate());
    }
}
