package ru.soune.nocopy.service.monitoring;

import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.entity.notification.NotificationMessage;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.mail.EmailService;
import ru.soune.nocopy.service.notification.NotificationService;
import ru.soune.nocopy.service.search.SearchImageService;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import ru.soune.nocopy.service.tariff.TariffService;
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;

@Slf4j
@Service
@RequiredArgsConstructor
public class MonitoringSearchService {
    private final SearchImageService searchImageService;

    private final FileMonitoringRepository monitoringRepository;

    private final TariffInfoService tariffInfoService;

    private final TariffService tariffService;

    private final EmailService emailService;

    private final UserRepository userRepository;

    private final ViolationService violationService;

    private final SearchProperties searchProperties;

    private final NotificationService notificationService;

    private final MessageSource messageSource;

    @Transactional(noRollbackFor = TariffNotFoundException.class)
    public void processFileSearch(FileMonitoringEntity monitoring) throws MessagingException, IOException {
        monitoring.setLastRun(LocalDateTime.now());
        monitoring.setLastRunStatus("IN_PROGRESS");
        monitoringRepository.save(monitoring);

        MonitoringType monitoringType = monitoring.getMonitoringType();
        TariffDTO tariffMonitoring = tariffService.getTariffByType(TariffType.valueOf(monitoringType.name()));
        User user = userRepository.findById(monitoring.getUserId()).orElseThrow();

        try {
            tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens(), OperationType.MONITORING);

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

            log.info("Monitoring search settings: useYandex={}, useGoogle={}", useYandex, useGoogle);

            List<YandexSearchResponse.ImageResult> allResults = new ArrayList<>();

            if (useYandex) {
                try {
                    String yandexResponse = searchImageService.searchReverseByPublicUrl(
                            monitoring.getFile(), "yandex_reverse_image", "visual_matches");

                    List<YandexSearchResponse.ImageResult> yandexImages =
                            searchImageService.getAllImagesWithoutPagination(yandexResponse, "visual_matches");

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

                } catch (TimeoutException | IOException e) {
                    log.error("Error processing monitoring search", e);
                    monitoring.setLastRunStatus("ERROR: " + e.getMessage());
                    updateNextRun(monitoring);

                    monitoringRepository.save(monitoring);

                    notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
                }
            } else {
                log.info("Yandex search is disabled by settings");
            }

            if (useGoogle) {
                try {
                    String googleResponse = searchImageService.searchReverseByPublicUrl(
                            monitoring.getFile(), "google_lens", "exact_matches");

                    List<YandexSearchResponse.ImageResult> googleImages =
                            searchImageService.getAllImagesWithoutPagination(googleResponse, "exact_matches");

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

                } catch (TimeoutException | IOException e) {
                    log.error("Error processing monitoring search", e);
                    monitoring.setLastRunStatus("ERROR: " + e.getMessage());
                    updateNextRun(monitoring);

                    monitoringRepository.save(monitoring);

                    notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
                }
            } else {
                log.info("Google search is disabled by settings");
            }

            List<YandexSearchResponse.ImageResult> uniqueResults = allResults;
            if (useYandex && useGoogle) {
                uniqueResults = searchImageService.removeDuplicateUrls(
                        useYandex ? allResults : new ArrayList<>(),
                        useGoogle ? allResults : new ArrayList<>()
                );
                log.info("After deduplication: {} unique results", uniqueResults.size());
            }

            for (YandexSearchResponse.ImageResult imageResult : uniqueResults) {
                violationService.processViolation(imageResult, monitoring.getFile(), null);
            }

            monitoring.setLastRunStatus("SUCCESS");
            updateNextRun(monitoring);
            monitoringRepository.save(monitoring);
            if (uniqueResults.isEmpty()) {
                log.info("No results found for monitoring file {}", monitoring.getFile().getId());
            }

        } catch (TariffNotFoundException e) {
            TariffInfo activeTariffInfo = user.getActiveTariffInfo();
            int currentTokens = activeTariffInfo.getTokens() + activeTariffInfo.getBoughtTokens();

            emailService.sendTokensNotFoundEmail(user, currentTokens, tariffMonitoring.getTokens());

            monitoring.setLastRunStatus("ERROR: " + e.getMessage());

            updateNextRun(monitoring);

            monitoringRepository.save(monitoring);

            notificationService.addNotification(NotificationType.TOKEN_NOT_FOUND, user.getId(),
                    NotificationMessage.FILE_SEARCH.getMessageKey());
        } catch (Exception e) {
            log.error("Error processing monitoring search", e);
            monitoring.setLastRunStatus("ERROR: " + e.getMessage());
            updateNextRun(monitoring);

            monitoringRepository.save(monitoring);

            notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
        }
    }

    private void updateNextRun(FileMonitoringEntity monitoring) {
        LocalDateTime nextRun = calculateNextRun(monitoring.getMonitoringType());
        monitoring.setNextRun(nextRun);
    }

    private LocalDateTime calculateNextRun(MonitoringType type) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextRun = now.withHour(2).withMinute(0).withSecond(0).withNano(0);

        if (now.isAfter(nextRun)) {
            nextRun = nextRun.plusDays(1);
        }

        return switch (type) {
            case MONITORING_DAILY -> nextRun;
            case MONITORING_WEEKLY -> nextRun.plusWeeks(1);
            case MONITORING_MONTHLY -> nextRun.plusMonths(1);
            default -> null;
        };
    }
}
