package ru.soune.nocopy.service.violation;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.dto.violation.FileViolationSummaryDTO;
import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.search.GlobalSearchResult;
import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.repository.ComplaintEntityRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.LawCaseRepository;
import ru.soune.nocopy.repository.ViolationRepository;
import ru.soune.nocopy.service.file.FileEntityService;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
@Slf4j
public class ViolationService {

    private final ViolationRepository violationRepository;

    private final FileEntityRepository fileEntityRepository;

    private final FileEntityService fileEntityService;

    private final ComplaintEntityRepository complaintRepository;

    private final LawCaseRepository lawCaseRepository;

    public void processViolation(YandexSearchResponse.ImageResult imageResult, FileEntity file,
                                 GlobalSearchResult result) {
        String url = imageResult.getUrl();
        String urlHash = DigestUtils.sha256Hex(url);

        if (!violationRepository.existsByUrlHash(urlHash)) {
            Violation violation = new Violation();

            if (result != null) {
                violation.setGlobalSearchResult(result);
            }

            violation.setHost(imageResult.getHost());
            violation.setUrl(url);
            violation.setUrlHash(urlHash);
            violation.setPageUrl(imageResult.getPageUrl());
            violation.setPageTitle(imageResult.getPageTitle());
            violation.setFileEntity(file);
            violation.setCreatedDate(LocalDateTime.now());
            violation.setStatus(ViolationStatus.NEW.name());

            violationRepository.save(violation);
        }
    }

    public Violation getById(Long violationId) {
        return violationRepository.findById(violationId).orElse(null);
    }

    public Page<FileViolationSummaryDTO> getFileViolationsSummary(Long userId, String fileName, String sortDirection,
                                                                  Integer page, Integer size, String start, String end) {
        List<FileEntity> userFiles = fileName == null || fileName.isEmpty()
                ? fileEntityService.getAllUserFiles(userId)
                : fileEntityService.getAllUserFilesWithFileName(userId, fileName,
                List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION));

        List<Violation> allViolations;
        if (start == null || end == null) {
            allViolations = violationRepository.findByFileEntityInAndStatusNot(userFiles,
                    ViolationStatus.NOT_OWNER_FILE.name());
        } else {
            allViolations = violationRepository.findByFileEntityInAndCreatedDateBetweenAndStatusNot(
                    userFiles, parseDate(start), parseDate(end),
                    ViolationStatus.NOT_OWNER_FILE.name());
        }

        Map<String, List<Violation>> violationsByFileId = allViolations.stream()
                .collect(Collectors.groupingBy(v -> v.getFileEntity().getId()));

        List<FileViolationSummaryDTO> allDTOs = violationsByFileId.entrySet().stream()
                .map(entry -> {
                    List<Violation> fileViolations = entry.getValue();
                    List<Long> violationIds = fileViolations.stream().map(Violation::getId).toList();
                    FileEntity file = fileViolations.get(0).getFileEntity();

                    LocalDateTime latestDate = fileViolations.stream()
                            .map(Violation::getCreatedDate)
                            .max(LocalDateTime::compareTo)
                            .orElse(null);

                    return FileViolationSummaryDTO.builder()
                            .fileId(file.getId())
                            .supportId(file.getSupportId())
                            .fileName(file.getOriginalFileName())
                            .fileSize(file.getFileSize())
                            .mimeType(file.getMimeType())
                            .status(file.getStatus())
                            .createdAt(file.getCreatedAt())
                            .violationCount(fileViolations.size())
                            .latestViolationDate(latestDate)
                            .countComplaint(complaintRepository.countComplaintByViolations(violationIds))
                            .countLawCase(lawCaseRepository.countLawCaseByViolationIds(violationIds))
                            .build();
                })
                .collect(Collectors.toList());

        Comparator<FileViolationSummaryDTO> comparator = Comparator.comparing(
                FileViolationSummaryDTO::getLatestViolationDate,
                sortDirection.equalsIgnoreCase("desc") ? Comparator.reverseOrder(): Comparator.naturalOrder());
        allDTOs.sort(comparator);

        int startIdx = page * size;
        int endIdx = Math.min(startIdx + size, allDTOs.size());
        List<FileViolationSummaryDTO> pageContent = startIdx < allDTOs.size()
                ? allDTOs.subList(startIdx, endIdx)
                : Collections.emptyList();

        return new PageImpl<>(pageContent, PageRequest.of(page, size), allDTOs.size());
    }
//    public Page<FileViolationSummaryDTO> getFileViolationsSummary(Long userId, String fileName, String sortDirection,
//                                                                  Integer page, Integer size, String start, String end) {
//        List<FileEntity> userFiles = fileName == null || fileName.isEmpty() ? fileEntityService.getAllUserFiles(userId)
//                : fileEntityService.getAllUserFilesWithFileName(userId, fileName,
//                List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION));
//
//        Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC,
//                "createdDate");
//        Pageable pageable = PageRequest.of(page, size, sort);
//        Page<Violation> violations;
//
//        if (start == null || end == null) {
//            violations = violationRepository.findByFileEntityInAndStatusNot(userFiles,
//                    ViolationStatus.NOT_OWNER_FILE.name(),pageable);
//        } else {
//            violations = violationRepository.findByFileEntityInAndStatusNotAndCreatedDateBetween(
//                    userFiles, ViolationStatus.NOT_OWNER_FILE.name(), parseDate(start), parseDate(end), pageable);
//        }
//
//        Map<String, List<Violation>> violationsByFileId = violations.getContent().stream()
//                .collect(Collectors.groupingBy(v -> v.getFileEntity().getId()));
//
//        List<FileViolationSummaryDTO> dtoList = violationsByFileId.entrySet().stream()
//                .map(entry -> {
//                    List<Violation> fileViolations = entry.getValue();
//                    FileEntity file = fileViolations.get(0).getFileEntity();
//
//                    LocalDateTime latestDate = fileViolations.stream()
//                            .map(Violation::getCreatedDate)
//                            .max(LocalDateTime::compareTo)
//                            .orElse(null);
//
//                    return FileViolationSummaryDTO.builder()
//                            .fileId(file.getId())
//                            .supportId(file.getSupportId())
//                            .fileName(file.getOriginalFileName())
//                            .fileSize(file.getFileSize())
//                            .mimeType(file.getMimeType())
//                            .status(file.getStatus())
//                            .createdAt(file.getCreatedAt())
//                            .violationCount(fileViolations.size())
//                            .latestViolationDate(latestDate)
//                            .build();
//                })
//                .collect(Collectors.toList());
//
//        return new PageImpl<>(dtoList, pageable, violations.getTotalElements());
//    }

    public List<FileViolationSummaryDTO> getFileViolationsSummary(Long userId) {
        List<FileEntity> userFiles =  fileEntityService.getAllUserFiles(userId);

        List<Violation> violations = violationRepository.findByFileEntityInAndStatusNot( userFiles,
                ViolationStatus.NOT_OWNER_FILE.name());

        Map<String, List<Violation>> violationsByFileId = violations.stream()
                .collect(Collectors.groupingBy(v -> v.getFileEntity().getId()));

        List<FileViolationSummaryDTO> dtoList = violationsByFileId.entrySet().stream()
                .map(entry -> {
                    List<Violation> fileViolations = entry.getValue();
                    FileEntity file = fileViolations.get(0).getFileEntity();

                    LocalDateTime latestDate = fileViolations.stream()
                            .map(Violation::getCreatedDate)
                            .max(LocalDateTime::compareTo)
                            .orElse(null);

                    return FileViolationSummaryDTO.builder()
                            .fileId(file.getId())
                            .supportId(file.getSupportId())
                            .fileName(file.getOriginalFileName())
                            .fileSize(file.getFileSize())
                            .mimeType(file.getMimeType())
                            .status(file.getStatus())
                            .createdAt(file.getCreatedAt())
                            .violationCount(fileViolations.size())
                            .latestViolationDate(latestDate)
                            .build();
                })
                .collect(Collectors.toList());

        return dtoList;
    }

    @Transactional
    public void changeStatus(ViolationStatus status, Long violationId) {
        Violation violation = violationRepository.findById(violationId).orElseThrow();
        violation.setStatus(status.name());

        violationRepository.save(violation);
    }

    public Page<Violation> getViolationsByFiles(List<FileEntity> files, int page, int size, String sortDirection) {
        Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC,
                "createdDate");
        Pageable pageable = PageRequest.of(page, size, sort);

        return violationRepository.findByFileEntityInAndStatusNot(files, ViolationStatus.NOT_OWNER_FILE.name(), pageable);
    }

    public Page<Violation> getViolationsByFilesAndStatus(List<FileEntity> files, String status, int page, int size, String sortDirection) {
        Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC,
                "createdDate");
        Pageable pageable = PageRequest.of(page, size, sort);

        return violationRepository.findByFileEntityInAndStatus(files, status, pageable);
    }

    public Page<Violation> getViolationsByFilesAndDateRange(List<FileEntity> files, LocalDateTime startDate, LocalDateTime endDate,
                                                            int page, int size, String sortDirection) {
        Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC,
                "createdDate");
        Pageable pageable = PageRequest.of(page, size, sort);

        return violationRepository.findByFileEntityInAndStatusNotAndCreatedDateBetween(files,
                ViolationStatus.NOT_OWNER_FILE.name(), startDate, endDate, pageable);
    }

    public Map<String, Long> getGroupedViolations(List<FileEntity> files, String groupBy, String status,
                                                  LocalDateTime startDate, LocalDateTime endDate,
                                                  String sortDirection) {
        List<Violation> violations;

        if (status != null && !status.isEmpty() && startDate != null && endDate != null) {
            violations = violationRepository.findByFileEntityInAndStatusAndCreatedDateBetween(files, status,
                    startDate, endDate);
        } else if (status != null && !status.isEmpty()) {
            violations = violationRepository.findByFileEntityInAndStatus(files, status);
        } else if (startDate != null && endDate != null) {
            violations = violationRepository.findByFileEntityInAndCreatedDateBetweenAndStatusNot(files, startDate,
                    endDate, ViolationStatus.NOT_OWNER_FILE.name());
        } else {
            violations = violationRepository.findByFileEntityInAndStatusNot(files,
                    ViolationStatus.NOT_OWNER_FILE.name());
        }

        Map<String, Long> grouped = violations.stream()
                .collect(Collectors.groupingBy(
                        v -> extractGroupKey(v.getPageUrl(), groupBy),
                        Collectors.counting()));

        return grouped.entrySet().stream()
                .sorted((e1, e2) -> {
                    if (sortDirection.equalsIgnoreCase("desc")) {
                        return e2.getValue().compareTo(e1.getValue());
                    } else {
                        return e1.getValue().compareTo(e2.getValue());
                    }
                })
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new));
    }

    public ViolationStatisticsResponse getViolationStatistics(Long userId, String fileId,
                                                              LocalDateTime startDate, LocalDateTime endDate) {

        List<FileEntity> userFiles;

        if (fileId != null && !fileId.isEmpty()) {
            FileEntity file = fileEntityRepository.findById(fileId)
                    .orElseThrow(() -> new IllegalArgumentException("File not found with id: " + fileId));

            if (!file.getUserId().equals(userId)) {
                throw new IllegalArgumentException("File does not belong to user");
            }

            userFiles = List.of(file);
        } else {
            userFiles = fileEntityService.getAllUserFiles(userId);
        }

        if (userFiles.isEmpty()) {
            return ViolationStatisticsResponse.builder()
                    .totalViolations(0)
                    .newViolations(0)
                    .inProgressViolations(0)
                    .resolvedViolations(0)
                    .build();
        }

        List<Violation> violations;

        if (startDate != null && endDate != null) {
            violations = violationRepository.findByFileEntityInAndCreatedDateBetweenAndStatusNot(userFiles,
                    startDate, endDate, ViolationStatus.NOT_OWNER_FILE.name());
        } else {
            violations = violationRepository.findByFileEntityInAndStatusNot(userFiles,
                    ViolationStatus.NOT_OWNER_FILE.name());
        }

        Set<String> inProgressStatuses = Set.of(
                ViolationStatus.LEGAL_IN_WORK.name(),
                ViolationStatus.COMPLAINT_IN_WORK.name(),
                ViolationStatus.COMPLAINT_AND_LEGAL_IN_WORK.name());

        List<Long> violationIds = violations.stream().map(Violation::getId).toList();

        long newViolations = violations.stream()
                .filter(v -> v.getStatus().equals(ViolationStatus.NEW.name()))
                .count();

        long inProgressViolations = violations.stream()
                .filter(v -> inProgressStatuses.contains(v.getStatus()))
                .count();

        long resolvedViolations = violations.stream()
                .filter(v -> v.getStatus().equals(ViolationStatus.AUTHORIZED_USE.name()))
                .count();

        Long totalComplaints = 0L;
        Long totalLawCases = 0L;

        if (!violationIds.isEmpty()) {
            totalComplaints = complaintRepository.countComplaintByViolations(violationIds);
            totalLawCases = lawCaseRepository.countLawCaseByViolationIds(violationIds);
        }

        return ViolationStatisticsResponse.builder()
                .totalViolations(violations.size())
                .newViolations(newViolations)
                .inProgressViolations(inProgressViolations)
                .resolvedViolations(resolvedViolations)
                .totalComplaints(totalComplaints)
                .totalLawCases(totalLawCases)
                .build();
    }

    private String extractGroupKey(String url, String groupBy) {
        try {
            String domain = url.replaceAll("https?://(www\\.)?", "").split("/")[0];
            if ("tld".equalsIgnoreCase(groupBy)) {
                int lastDot = domain.lastIndexOf('.');
                return lastDot > 0 ? domain.substring(lastDot + 1) : domain;
            }
            return domain;
        } catch (Exception e) {
            return "unknown";
        }
    }

    private LocalDateTime parseDate(String dateStr) {
        try {
            return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        } catch (DateTimeParseException e) {
            log.error(e.getMessage());
            return null;
        }
    }
}