package ru.soune.nocopy.service.file.moderation;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.file.moderation.ModerationLog;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.exception.FileEntityNotFoundException;

import ru.soune.nocopy.exception.InvalidAppealException;
import ru.soune.nocopy.repository.FileAppealRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.ModerationLogRepository;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.notification.NotificationService;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Service
@RequiredArgsConstructor
public class ModerationService {
    private final FileEntityRepository fileEntityRepository;

    private final FileAppealRepository fileAppealRepository;

    private final ModerationLogRepository moderationLogRepository;

    private final FileEntityService fileEntityService;

    private final NotificationService notificationService;

    private static final List<FileStatus> MODERATION_NEEDED_STATUSES = Arrays.asList(
            FileStatus.MODERATION,
            FileStatus.BLOCKED
    );

    /**
     * Модерация файла администратором
     */
    @Transactional
    public void moderateFile(ModerationActionRequest request, Long moderatorId) {
        FileEntity file = fileEntityRepository.findById(request.getFileId())
                .orElseThrow(() -> new FileEntityNotFoundException(request.getFileId()));

        if (!MODERATION_NEEDED_STATUSES.contains(file.getStatus())) {
            throw new IllegalStateException("File is not in moderation state. Current status: " + file.getStatus());
        }

        ModerationLog log = ModerationLog.builder()
                .fileId(file.getId())
                .moderatorId(moderatorId)
                .oldStatus(file.getStatus())
                .newStatus(request.getNewStatus())
                .reason(request.getComment())
                .comment(request.getComment())
                .build();

        moderationLogRepository.save(log);

        file.setStatus(request.getNewStatus());

        fileEntityRepository.save(file);

        if (request.getNewStatus() == FileStatus.BLOCKED ||
                request.getNewStatus() == FileStatus.VIOLATION) {

            fileAppealRepository.findByFileIdAndStatus(file.getId(), AppealStatus.PENDING)
                    .ifPresent(appeal -> {
                        appeal.setStatus(AppealStatus.REJECTED);
                        appeal.setAdminComment(request.getComment());
                        appeal.setResolvedAt(LocalDateTime.now());
                        fileAppealRepository.save(appeal);
                    });
        } else if (request.getNewStatus() == FileStatus.REMOVED) {
            try {
                fileEntityService.softDeleteFileWithHash(file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        notificationService.addNotification(NotificationType.FILE_MODERATION_EVENT, file.getUserId());
    }

    /**
     * Подача апелляции пользователем
     */
    @Transactional
    public AppealResponse submitAppeal(AppealRequest request, Long userId) {
        FileEntity file = fileEntityRepository.findById(request.getFileId())
                .orElseThrow(() -> new FileEntityNotFoundException(request.getFileId()));

        if (!file.getUserId().equals(userId)) {
            throw new SecurityException("User is not the owner of this file");
        }

        if (!canAppeal(file)) {
            throw new InvalidAppealException("Cannot appeal this file. Current status: " + file.getStatus());
        }

        if (hasActiveAppeal(file.getId())) {
            throw new InvalidAppealException("Active appeal already exists for this file");
        }

        FileAppeal appeal = FileAppeal.builder()
                .fileId(file.getId())
                .userId(userId)
                .appealReason(request.getAppealReason())
                .additionalInfo(request.getAdditionalInfo())
                .status(AppealStatus.PENDING)
                .moderationBeforeStatus(file.getStatus())
                .build();

        FileAppeal saved = fileAppealRepository.save(appeal);

        return convertToResponse(saved);
    }

    /**
     * Рассмотрение апелляции администратором
     */
    @Transactional
    public void reviewAppeal(String appealId, boolean approve, String comment, Long moderatorId) {
        FileAppeal appeal = fileAppealRepository.findById(appealId)
                .orElseThrow(() -> new RuntimeException("Appeal not found: " + appealId));

        if (appeal.getStatus() != AppealStatus.PENDING) {
            throw new IllegalStateException("Appeal already processed");
        }

        FileEntity file = fileEntityRepository.findById(appeal.getFileId())
                .orElseThrow(() -> new FileEntityNotFoundException(appeal.getFileId()));

        appeal.setStatus(approve ? AppealStatus.APPROVED : AppealStatus.REJECTED);
        appeal.setAdminComment(comment);
        appeal.setModeratorId(moderatorId);
        appeal.setResolvedAt(LocalDateTime.now());

        if (approve) {
            file.setStatus(FileStatus.ACTIVE);
        }

        ModerationLog log = ModerationLog.builder()
                .fileId(file.getId())
                .moderatorId(moderatorId)
                .oldStatus(file.getStatus())
                .newStatus(file.getStatus())
                .comment("Appeal review: " + comment)
                .build();
        moderationLogRepository.save(log);

        fileEntityRepository.save(file);

        fileAppealRepository.save(appeal);
    }

    /**
     * Проверка, можно ли подать апелляцию на файл
     */
    public boolean canAppeal(FileEntity file) {
        return file.getStatus() == FileStatus.BLOCKED;
    }

    /**
     * Проверка наличия активной апелляции
     */
    public boolean hasActiveAppeal(String fileId) {
        return fileAppealRepository.existsByFileIdAndStatusIn(
                fileId,
                Arrays.asList(AppealStatus.PENDING, AppealStatus.IN_REVIEW)
        );
    }

    /**
     * Получение информации о файле для модерации
     */
    @Transactional(readOnly = true)
    public FileModerationInfo getFileModerationInfo(String fileId) {
        FileEntity file = fileEntityRepository.findById(fileId)
                .orElseThrow(() -> new FileEntityNotFoundException(fileId));

        FileAppeal activeAppeal = fileAppealRepository.findByFileIdAndStatus(
                fileId, AppealStatus.PENDING).orElse(null);

        return FileModerationInfo.builder()
                .fileId(file.getId())
                .fileName(file.getOriginalFileName())
                .currentStatus(file.getStatus())
                .uploadDate(file.getCreatedAt())
                .hasActiveAppeal(activeAppeal != null)
                .activeAppeal(activeAppeal != null ?
                        AppealInfo.builder()
                                .appealId(activeAppeal.getId())
                                .status(activeAppeal.getStatus().name())
                                .createdAt(activeAppeal.getCreatedAt())
                                .build() : null)
                .build();
    }

    /**
     * Получение списка файлов на модерации
     */
    @Transactional(readOnly = true)
    public Page<FileEntity> getFilesForModeration(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return fileEntityRepository.findByStatusIn(
                Arrays.asList(FileStatus.MODERATION, FileStatus.BLOCKED),
                pageable
        );
    }

    /**
     * Получение апелляций пользователя
     */
    @Transactional(readOnly = true)
    public Page<AppealResponse> getUserAppeals(Long userId, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return fileAppealRepository.findByUserId(userId, pageable)
                .map(this::convertToResponse);
    }

    private AppealResponse convertToResponse(FileAppeal appeal) {
        return AppealResponse.builder()
                .appealId(appeal.getId())
                .fileId(appeal.getFileId())
                .appealReason(appeal.getAppealReason())
                .additionalInfo(appeal.getAdditionalInfo())
                .status(appeal.getStatus().name())
                .adminComment(appeal.getAdminComment())
                .createdAt(appeal.getCreatedAt())
                .resolvedAt(appeal.getResolvedAt())
                .build();
    }
}
