package ru.soune.nocopy.service.search;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.search.GlobalSearchResult;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.GlobalSearchResultRepository;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
import ru.soune.nocopy.service.notification.NotificationService;
import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import ru.soune.nocopy.service.violation.ViolationService;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class GlobalSearchAsyncProcessor {

    private final GlobalSearchResultRepository globalSearchResultRepository;

    private final SearchImageService searchImageService;

    private final TariffInfoService tariffInfoService;

    private final GlobalSearchTaskRepository globalSearchTaskRepository;

    private final ViolationService violationService;

    private final SearchProperties searchProperties;

    private final NotificationService notificationService;

    private final FileEntityRepository fileEntityRepository;

    @Async
    @Transactional
    public void processFilesAsync(String taskId, List<String> filesToProcess, Long userId) {
        GlobalSearchTask task = globalSearchTaskRepository.findById(taskId).orElseThrow();

        try {
            for (int i = 0; i < filesToProcess.size(); i++) {
                String uuid = filesToProcess.get(i);
                FileEntity file = fileEntityRepository.findById(uuid)
                        .orElseThrow(() -> new RuntimeException("File not found: " + uuid));

                tariffInfoService.writeOffTokens(userId, TariffConstants.TOKEN_VALUE_FOR_SEARCH, OperationType.GLOBAL_SEARCH);
                GlobalSearchResult result = processFile(file, taskId, userId);
                globalSearchResultRepository.save(result);

                task.setProcessedFiles(i + 1);
                task.setUpdatedAt(LocalDateTime.now());
                globalSearchTaskRepository.save(task);
            }

            task.setStatus(SearchStatus.COMPLETED.name());
            task.setUpdatedAt(LocalDateTime.now());
            globalSearchTaskRepository.save(task);

            notificationService.addNotification(NotificationType.SEARCH_RESULT, userId);
        } catch (Exception e) {
            log.error("Global search failed for task: {}", taskId, e);

            task.setStatus(SearchStatus.FAILED.name());
            task.setUpdatedAt(LocalDateTime.now());
            globalSearchTaskRepository.save(task);
        }
    }

    private GlobalSearchResult processFile(FileEntity file, String taskId, Long userId) {
        GlobalSearchResult result = new GlobalSearchResult();
        result.setTaskId(taskId);
        result.setFileId(file.getId());
        result.setFileName(file.getOriginalFileName());

        result = globalSearchResultRepository.save(result);

        boolean useGoogle = searchProperties.getEngines().getOrDefault("google",
                new SearchProperties.EngineConfig()).isEnabled();
        boolean useYandex = searchProperties.getEngines().getOrDefault("yandex",
                new SearchProperties.EngineConfig()).isEnabled();

        log.info("Global search settings for file {}: useGoogle={}, useYandex={}",
                file.getId(), useGoogle, useYandex);

        List<YandexSearchResponse.ImageResult> googleImages = new ArrayList<>();
        List<YandexSearchResponse.ImageResult> yandexImages = new ArrayList<>();
        List<YandexSearchResponse.ImageResult> allResults = new ArrayList<>();
        boolean hasTimeout = false;

        if (useGoogle) {
            try {
                String searchResponseGoogle = searchImageService.searchReverseByPublicUrl(
                        file, "google_lens", "exact_matches");

                googleImages = searchImageService.getAllImagesWithoutPagination(searchResponseGoogle,
                        "exact_matches");

                allResults.addAll(googleImages);
                log.info("Google search OK for file {}, found {} results", file.getId(), googleImages.size());

            } catch (TimeoutException e) {
                log.warn("Google search timeout for file {}", file.getId());
                hasTimeout = true;
            } catch (IOException e) {
                log.error("Google search failed for file {}", file.getId(), e);
            }
        } else {
            log.info("Google search is disabled by settings for file {}", file.getId());
        }

        if (useYandex) {
            try {
                String searchResponseYandex = searchImageService.searchReverseByPublicUrl(
                        file, "yandex_reverse_image", "visual_matches");

                yandexImages = searchImageService.getAllImagesWithoutPagination(searchResponseYandex, "visual_matches");

                allResults.addAll(yandexImages);
                log.info("Yandex search OK for file {}, found {} results", file.getId(), yandexImages.size());

            } catch (TimeoutException e) {
                log.warn("Yandex search timeout for file {}", file.getId());
                hasTimeout = true;
            } catch (IOException e) {
                log.error("Yandex search failed for file {}", file.getId(), e);
            }
        } else {
            log.info("Yandex search is disabled by settings for file {}", file.getId());
        }

        List<YandexSearchResponse.ImageResult> allUniqueImages;
        if (useGoogle && useYandex) {
            allUniqueImages = searchImageService.removeDuplicateUrls(
                    yandexImages.stream()
                            .filter(img -> img.getUrl() != null)
                            .collect(Collectors.toList()),
                    googleImages.stream()
                            .filter(img -> img.getUrl() != null)
                            .collect(Collectors.toList())
            );
            log.info("Merged results from both engines: {} unique images", allUniqueImages.size());
        } else if (useGoogle) {
            allUniqueImages = googleImages.stream()
                    .filter(img -> img.getUrl() != null)
                    .collect(Collectors.toList());
            log.info("Results only from Google: {} images", allUniqueImages.size());
        } else if (useYandex) {
            allUniqueImages = yandexImages.stream()
                    .filter(img -> img.getUrl() != null)
                    .collect(Collectors.toList());
            log.info("Results only from Yandex: {} images", allUniqueImages.size());
        } else {
            allUniqueImages = new ArrayList<>();
            log.warn("All search engines are disabled for file {}", file.getId());
        }

        for (YandexSearchResponse.ImageResult imageResult : allUniqueImages) {
            violationService.processViolation(imageResult, file, result);
        }

        if (allUniqueImages.isEmpty() && hasTimeout) {
            result.setFileStatus(SearchStatus.TIMEOUT.name());
        } else if (allUniqueImages.isEmpty() && (!useGoogle && !useYandex)) {
            result.setFileStatus(SearchStatus.FAILED.name());
            log.warn("No results for file {} because all engines are disabled", file.getId());
        } else {
            result.setFileStatus(SearchStatus.SUCCESS.name());
        }

        return result;
    }
}
