package at.procon.dip.domain.ted.service;

import at.procon.dip.domain.ted.config.TedProjectionProperties;
import at.procon.dip.domain.ted.search.TedStructuredSearchRepository;
import at.procon.dip.domain.ted.search.dto.TedStructuredSearchFacets;
import at.procon.dip.domain.ted.search.dto.TedStructuredSearchFilter;
import at.procon.dip.domain.ted.search.dto.TedStructuredSearchSummaryRow;
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import at.procon.dip.search.dto.SearchMode;
import at.procon.dip.search.dto.SearchSortMode;
import at.procon.dip.search.spi.SearchDocumentScope;
import at.procon.dip.search.service.SearchOrchestrator;
import at.procon.ted.model.dto.DocumentDtos.DocumentSummary;
import at.procon.ted.model.dto.DocumentDtos.SearchRequest;
import at.procon.ted.model.dto.DocumentDtos.SearchResponse;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Service
@RequiredArgsConstructor
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
@Transactional(readOnly = true)
public class TedStructuredSearchService {

    private final TedStructuredSearchRepository repository;
    private final SearchOrchestrator searchOrchestrator;
    private final TedProjectionProperties tedProjectionProperties;

    public SearchResponse search(SearchRequest request) {
        int page = request.getPage() != null && request.getPage() >= 0 ? request.getPage() : 0;
        int size = request.getSize() != null && request.getSize() > 0 ? request.getSize() : 20;
        TedStructuredSearchFilter filter = toFilter(request);
        int facetLimit = request.getFacetBucketLimit() != null && request.getFacetBucketLimit() > 0
                ? request.getFacetBucketLimit()
                : tedProjectionProperties.getStructuredSearchFacetBucketLimit();
        TedStructuredSearchFacets facets = Boolean.FALSE.equals(request.getIncludeFacets())
                ? null
                : repository.computeFacets(filter, facetLimit);

        SearchResponse response = hasQuery(request)
                ? searchHybrid(request, filter, page, size)
                : searchStructuredOnly(request, filter, page, size);
        response.setFacets(facets);
        return response;
    }

    private SearchResponse searchStructuredOnly(SearchRequest request,
                                                TedStructuredSearchFilter filter,
                                                int page,
                                                int size) {
        long total = repository.countDistinctDocuments(filter);
        List<TedStructuredSearchSummaryRow> rows = repository.searchStructured(filter, page, size, request.getSortBy(), request.getSortDirection());
        return SearchResponse.builder()
                .documents(rows.stream().map(this::toSummary).toList())
                .page(page)
                .size(size)
                .totalElements(total)
                .totalPages((int) Math.ceil(total / (double) size))
                .hasNext((page + 1L) * size < total)
                .hasPrevious(page > 0)
                .build();
    }

    private SearchResponse searchHybrid(SearchRequest request,
                                        TedStructuredSearchFilter filter,
                                        int page,
                                        int size) {
        List<UUID> candidateIds = repository.findCandidateDocumentIds(filter, tedProjectionProperties.getStructuredSearchHybridCandidateLimit());
        if (candidateIds.isEmpty()) {
            return SearchResponse.builder()
                    .documents(List.of())
                    .page(page)
                    .size(size)
                    .totalElements(0)
                    .totalPages(0)
                    .hasNext(false)
                    .hasPrevious(page > 0)
                    .build();
        }

        at.procon.dip.search.dto.SearchRequest genericRequest = at.procon.dip.search.dto.SearchRequest.builder()
                .queryText(request.getSemanticQuery())
                .modes(Set.of(SearchMode.HYBRID))
                .page(page)
                .size(size)
                .sortMode(resolveSortMode(request.getSortBy(), request.getSortDirection()))
                .semanticSimilarityThreshold(request.getSimilarityThreshold())
                .build();

        var genericResponse = searchOrchestrator.search(
                genericRequest,
                new SearchDocumentScope(Set.of(), null, null, null, null, Set.copyOf(candidateIds))
        );

        List<UUID> orderedIds = genericResponse.getHits().stream().map(hit -> hit.getDocumentId()).toList();
        Map<UUID, TedStructuredSearchSummaryRow> summaryById = repository.findSummariesByDocumentIds(orderedIds).stream()
                .collect(Collectors.toMap(TedStructuredSearchSummaryRow::documentId, row -> row, (a, b) -> a, LinkedHashMap::new));

        List<DocumentSummary> docs = genericResponse.getHits().stream()
                .map(hit -> {
                    TedStructuredSearchSummaryRow row = summaryById.get(hit.getDocumentId());
                    if (row == null) {
                        return null;
                    }
                    DocumentSummary summary = toSummary(row);
                    summary.setSimilarity(hit.getFinalScore());
                    return summary;
                })
                .filter(java.util.Objects::nonNull)
                .toList();

        return SearchResponse.builder()
                .documents(docs)
                .page(page)
                .size(size)
                .totalElements(genericResponse.getTotalHits())
                .totalPages((int) Math.ceil(genericResponse.getTotalHits() / (double) size))
                .hasNext((page + 1L) * size < genericResponse.getTotalHits())
                .hasPrevious(page > 0)
                .build();
    }

    private boolean hasQuery(SearchRequest request) {
        return StringUtils.hasText(request.getSemanticQuery());
    }

    private TedStructuredSearchFilter toFilter(SearchRequest request) {
        return TedStructuredSearchFilter.builder()
                .countryCode(request.getCountryCode())
                .countryCodes(request.getCountryCodes())
                .noticeType(request.getNoticeType())
                .contractNature(request.getContractNature())
                .procedureType(request.getProcedureType())
                .cpvPrefix(request.getCpvPrefix())
                .cpvCodes(request.getCpvCodes())
                .nutsCode(request.getNutsCode())
                .nutsCodes(request.getNutsCodes())
                .publicationDateFrom(request.getPublicationDateFrom())
                .publicationDateTo(request.getPublicationDateTo())
                .submissionDeadlineAfter(request.getSubmissionDeadlineAfter())
                .euFunded(request.getEuFunded())
                .buyerNameContains(request.getBuyerNameContains())
                .projectTitleContains(request.getProjectTitleContains())
                .build();
    }

    private SearchSortMode resolveSortMode(String sortBy, String sortDirection) {
        if ("projectTitle".equalsIgnoreCase(sortBy) && "asc".equalsIgnoreCase(sortDirection)) {
            return SearchSortMode.TITLE_ASC;
        }
        if ("publicationDate".equalsIgnoreCase(sortBy) || "submissionDeadline".equalsIgnoreCase(sortBy)) {
            return SearchSortMode.CREATED_AT_DESC;
        }
        return SearchSortMode.SCORE_DESC;
    }

    private DocumentSummary toSummary(TedStructuredSearchSummaryRow row) {
        return DocumentSummary.builder()
                .id(row.documentId())
                .publicationId(row.publicationId())
                .noticeId(row.noticeId())
                .noticeType(row.noticeType())
                .projectTitle(row.projectTitle())
                .buyerName(row.buyerName())
                .buyerCountryCode(row.buyerCountryCode())
                .buyerCity(row.buyerCity())
                .contractNature(row.contractNature())
                .procedureType(row.procedureType())
                .publicationDate(row.publicationDate())
                .submissionDeadline(row.submissionDeadline())
                .cpvCodes(row.cpvCodes())
                .totalLots(row.totalLots())
                .estimatedValue(row.estimatedValue())
                .estimatedValueCurrency(row.estimatedValueCurrency())
                .build();
    }
}
