package com.arms.api.evaluation.service;

import com.arms.api.evaluation.model.dto.EvaluationRequestDTO;
import com.arms.api.evaluation.model.vo.EvaluationResultVO;
import com.arms.api.evaluation.model.vo.FullEvaluationResultVO;
import com.arms.api.util.advisors.SimpleLogAdvisor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.evaluation.EvaluationRequest;
import org.springframework.ai.evaluation.EvaluationResponse;
import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
import org.springframework.ai.chat.evaluation.FactCheckingEvaluator;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import org.springframework.ai.document.Document;

import java.util.Collections;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class EvaluationServiceImpl implements EvaluationService {

    private final ChatModel chatModel;
    private final VectorStore vectorStore;
    private final SimpleLogAdvisor simpleLogAdvisor;

    @Override
    public Mono<EvaluationResultVO> evaluateRelevancy(EvaluationRequestDTO request) {
        return Mono.fromCallable(() -> {
            log.info("evaluate relevancy start");

            RelevancyEvaluator evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));

            EvaluationRequest evalRequest = new EvaluationRequest(
                    request.getUserText(),
                    toContextList(request.getContextDocuments()),
                    request.getResponseContent()
            );

            EvaluationResponse evalResponse = evaluator.evaluate(evalRequest);

            return EvaluationResultVO.builder()
                    .evaluationType("RELEVANCY")
                    .pass(evalResponse.isPass())
                    .score(evalResponse.isPass() ? 1.0f : 0.0f)
                    .feedback(evalResponse.isPass()
                            ? "AI 응답이 질문 및 문맥과 관련이 있습니다."
                            : "AI 응답이 질문 및 문맥과 관련이 없습니다.")
                    .build();
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<EvaluationResultVO> evaluateFactChecking(EvaluationRequestDTO request) {
        return Mono.fromCallable(() -> {
            log.info("FactChecking Start");

            FactCheckingEvaluator evaluator = new FactCheckingEvaluator(ChatClient.builder(chatModel));

            EvaluationRequest evalRequest = new EvaluationRequest(
                    request.getUserText(),
                    toContextList(request.getContextDocuments()),
                    request.getResponseContent()
            );

            EvaluationResponse evalResponse = evaluator.evaluate(evalRequest);

            return EvaluationResultVO.builder()
                    .evaluationType("FACT_CHECKING")
                    .pass(evalResponse.isPass())
                    .score(evalResponse.isPass() ? 1.0f : 0.0f)
                    .feedback(evalResponse.isPass()
                            ? "AI 응답이 문서에 의해 사실적으로 뒷받침됩니다."
                            : "AI 응답이 문서에 의해 뒷받침되지 않습니다.")
                    .build();
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<FullEvaluationResultVO> evaluateAll(EvaluationRequestDTO request) {
        log.info("evaluate all start");

        return evaluateRelevancy(request)
                .flatMap(relevancy -> evaluateFactChecking(request)
                        .map(factChecking -> FullEvaluationResultVO.builder()
                                .relevancy(relevancy)
                                .factChecking(factChecking)
                                .build()));
    }

    @Override
    public Mono<FullEvaluationResultVO> evaluateAllWithContext(EvaluationRequestDTO request) {
        return Mono.fromCallable(() -> {
            log.info("evaluateAllWithContext start");

            List<Document> documents = vectorStore.similaritySearch(
                    SearchRequest.builder()
                            .query(request.getUserText())
                            .topK(3)
                            .similarityThreshold(0.7)
                            .build()
            );

            List<String> contextDocs = documents.stream()
                    .map(Document::getText)
                    .toList();

            return EvaluationRequestDTO.builder()
                    .userText(request.getUserText())
                    .responseContent(request.getResponseContent())
                    .contextDocuments(contextDocs)
                    .build();
        }).subscribeOn(Schedulers.boundedElastic())
          .flatMap(this::evaluateAll);
    }

    private List<Document> toContextList(List<String> documents) {
        if (documents == null || documents.isEmpty()) {
            return Collections.emptyList();
        }
        return documents.stream()
                .map(doc -> Document.builder().text(doc).build())
                .toList();
    }
}
